Spring自带定时任务@Scheduled注解

2024-06-06 03:44

本文主要是介绍Spring自带定时任务@Scheduled注解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 1. cron表达式生成器
  • 2. 简单定时任务代码示例:每隔两秒打印一次字符
  • 3. @Scheduled注解的参数
    • 3.1 cron
    • 3.2 fixedDelay
    • 3.3 fixedRate
    • 3.4 initialDelay
    • 3.5 fixedDelayString、fixedRateString、initialDelayString等是String类型,支持占位符
    • 3.6 timeUnit
  • 4. 问题:定时器的任务默认是按照顺序执行的,可能导致一些任务无法执行
    • 4.1 ScheduledAnnotationBeanPostProcessor类处理器解析带有@Scheduled注解的方法
    • 4.2 processScheduled方法处理@Scheduled注解后面的参数,并将其添加到任务列表中
    • 4.3 执行任务。ScheduledTaskRegistrar类为Spring容器的定时任务注册中心。Spring容器通过线程处理注册的定时任务
  • 5. 问题:当系统时间发生改变时,@Scheduled注解失效

1. cron表达式生成器

cron表达式生成器:https://cron.qqe2.com/

2. 简单定时任务代码示例:每隔两秒打印一次字符

import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;@Service
@EnableScheduling
public class ScheduleDemo1 {@Scheduled(cron = "*/2 * * * * ?")public static void test() {// 十六进制转换为字符System.out.print((char) Integer.parseInt("5fc3", 16));System.out.print((char) Integer.parseInt("6d41", 16));System.out.print((char) Integer.parseInt("65f6", 16));System.out.print((char) Integer.parseInt("95f4", 16));System.out.println();}
}

输出:
在这里插入图片描述

3. @Scheduled注解的参数

在这里插入图片描述

3.1 cron

参数接收一个cron表达式,cron表达式是一个以空格为间隔符来区分不同域的字符串,总共有6个或7个域。cron表达式从左到右每个域分别标识的[秒] [分] [小时] [日] [月] [周] [年],其中[年]不是必选的域可以省略。

序号必填值的范围允许的通配符
10-59, - * /
20-59, - * /
30-23, - * /
41-31, - * ? / L W
51-12 / JAN-DEC, - * /
61-7 or SUN-SAT, - * ? / L #
71970-2099, - * /

通配符说明:

  • * 表示所有值,例如:在时的字段上设置 *,表示每一个小时都会触发。
  • ? 表示不指定值,即当前使用的场景为不需要关心这个字段设置的值。例如:要在每月的10号触发一个操作,但不关心是周几,所以需要周位置的那个字段设置为“?”, 具体设置为 0 0 0 10 * ? 。
  • - 表示区间,例如:在小时上设置 “10-12”,表示 10,11,12点都会触发。
  • , 表示指定多个值,例如在周字段上设置 “MON,WED,FRI” 表示周一,周三和周五触发。
  • / 用于递增触发,如在秒上面设置“5/15” 表示从5秒开始,每隔15秒触发(5,20,35,50)。在日字段上设置‘1/3’所示每月1号开始,每隔三天触发一次
  • L 表示最后的意思,在日字段设置上,表示当月的最后一天(依据当前月份,如果是二月还会依据是否是闰年), 在周字段上表示星期六,相当于“7”或“SAT”。如果在“L”前加上数字,则表示该数据的最后一个。例如在周字段上设置“6L”这样的格式,则表示“本月最后一个星期五”。
  • W表示离指定日期的最近那个工作日(周一至周五)。例如在日字段上置“15W”,表示离每月15号最近的那个工作日触发。如果15号正好是周六,则找最近的周五(14号)触发, 如果15号是周未,则找最近的下周一(16号)触发。如果15号正好在工作日(周一至周五),则就在该天触发。如果指定格式为 “1W”,它则表示每月1号往后最近的工作日触发。如果1号正是周六,则将在3号下周一触发。(注,“W”前只能设置具体的数字,不允许区间“-”)。
  • #序号(表示每月的第几个周几),例如在周字段上设置“6#3”表示在每月的第三个周六。注意如果指定“#5”,正好第五周没有周六,则不会触发该配置;小提示:‘L’和 ‘W’可以一组合使用。如果在日字段上设置“LW”,则表示在本月的最后一个工作日触发;周字段的设置,若使用英文字母是不区分大小写的,即MON与mon相同。

示例:

  • 每隔5秒执行一次:*/5 * * * * ?

  • 每隔1分钟执行一次:0 */1 * * * ?

  • 每天23点执行一次:0 0 23 * * ?

  • 每天凌晨1点执行一次:0 0 1 * * ?

  • 每月1号凌晨1点执行一次:0 0 1 1 * ?

3.2 fixedDelay

上一次执行完成后延迟多久执行下一次,以上一次任务执行的完成时间开始延迟,如:

@Scheduled(fixedDelay = 5000) //上一次执行完成后延迟5s再执行

3.3 fixedRate

固定延迟多久执行下一次任务,不依赖于上一次任务执行成功的时间,如:

@Scheduled(fixedRate= 5000) //上一次执行后延迟5s就开始执行

3.4 initialDelay

启动后延迟多久后执行第一次,可根据场景搭配fixedRate或fixedDelay实现定时调度,如:

@Scheduled(initialDelay = 5000,fixedRate= 300000) //启动后延迟5s执行,之后每次执行时间间隔5min

3.5 fixedDelayString、fixedRateString、initialDelayString等是String类型,支持占位符

如:@Scheduled(fixedDelayString = “${task.fixed-delay}”)

3.6 timeUnit

时间单位,默认毫秒

TimeUnit timeUnit() default TimeUnit.MILLISECONDS;

4. 问题:定时器的任务默认是按照顺序执行的,可能导致一些任务无法执行

我创建定时器执行任务目的是为了让它多线程执行任务,但是后来才发现,@Scheduled注解的方法默认是按照顺序执行的,这会导致当一个任务挂死的情况下,其它任务都在等待,无法执行。

@Scheduled注解加载的过程,以及它是如何执行的:
在这里插入图片描述

4.1 ScheduledAnnotationBeanPostProcessor类处理器解析带有@Scheduled注解的方法

在这里插入图片描述

4.2 processScheduled方法处理@Scheduled注解后面的参数,并将其添加到任务列表中

在这里插入图片描述

4.3 执行任务。ScheduledTaskRegistrar类为Spring容器的定时任务注册中心。Spring容器通过线程处理注册的定时任务

首先,调用scheduleCronTask初始化定时任务。

在这里插入图片描述
然后,在ThreadPoolTaskScheduler类中,会对线程池进行初始化,线程池的核心线程数量为1,

private volatile int poolSize = 1;

在这里插入图片描述
阻塞队列为DelayedWorkQueue。

在这里插入图片描述
因此,原因就找到了,当有多个方法使用@Scheduled注解时,就会创建多个定时任务到任务列表中,当其中一个任务没执行完时,其它任务在阻塞队列当中等待,因此,所有的任务都是按照顺序执行的,只不过由于任务执行的速度相当快,让我们感觉任务都是多线程执行的。

下面举例来验证一下,将上述的某个定时任务添加睡眠时间,观察另一个定时任务是否输出。

import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;import java.util.concurrent.TimeUnit;@Slf4j
@EnableScheduling
@Component
public class ScheduleDemo2 {private static final ThreadLocal<Integer> threadLocalA = new ThreadLocal<>();@Scheduled(cron = "0/2 * * * * ?")public void taskA() {try {log.info("执行了ScheduleTask类中的taskA方法");Thread.sleep(TimeUnit.SECONDS.toMillis(10));} catch (InterruptedException e) {e.printStackTrace();}}@Scheduled(cron = "0/1 * * * * ?")public void taskB() {int num = threadLocalA.get() == null ? 0 : threadLocalA.get();![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/2022840e83f049e2875381196e7c55ea.png)log.info("taskB方法执行次数:{}", ++num);threadLocalA.set(num);}
}

输出:可以观察到两个定时任务不是同时执行的,是按顺序执行的

在这里插入图片描述
想要避免顺序执行,进行并发,就要配置定时任务线程池:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledThreadPoolExecutor;@Configuration
public class ScheduleConfig implements SchedulingConfigurer {@Overridepublic void configureTasks(ScheduledTaskRegistrar taskRegistrar) {taskRegistrar.setScheduler(getExecutor());}@Beanpublic Executor getExecutor(){return new ScheduledThreadPoolExecutor(5);}
}

输出:可以观察到两个定时任务不是顺序执行了,从出现次数的乱序这种多线程问题也可以看出是并发执行了

在这里插入图片描述
从输出结果我们可以看到,即使testA休眠,但是testB仍然正常执行,并且其还复用了其它线程,导致执行次数发生了变化。

5. 问题:当系统时间发生改变时,@Scheduled注解失效

另外一种情况就是在配置完线程池之后,当你手动修改服务器时间时,目前我做的测试就是服务器时间调前,则会导致注解失效,而服务器时间调后,则不会影响注解的作用。

原因:

JVM启动之后会记录当前系统时间,然后JVM根据CPU ticks自己来算时间,此时获取的是定时任务的基准时间。如果此时将系统时间进行了修改,当Spring将之前获取的基准时间与当下获取的系统时间进行比对不一致,就会造成Spring内部定时任务失效。因为此时系统时间发生变化了,不会触发定时任务。

解决办法:

  1. 重启项目

  2. 不使用@Scheduled注解,改成ScheduledThreadPoolExecutor进行替代,部分代码:在这里插入图片描述

实际项目中一般使用xxl-job、Quartz等框架,@Scheduled注解会使用的话也是定时更新一些变量的值,大量的定时任务还是使用专门的定时任务框架实现

参考1
参考2
参考3

这篇关于Spring自带定时任务@Scheduled注解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1035002

相关文章

JSON字符串转成java的Map对象详细步骤

《JSON字符串转成java的Map对象详细步骤》:本文主要介绍如何将JSON字符串转换为Java对象的步骤,包括定义Element类、使用Jackson库解析JSON和添加依赖,文中通过代码介绍... 目录步骤 1: 定义 Element 类步骤 2: 使用 Jackson 库解析 jsON步骤 3: 添

Java中注解与元数据示例详解

《Java中注解与元数据示例详解》Java注解和元数据是编程中重要的概念,用于描述程序元素的属性和用途,:本文主要介绍Java中注解与元数据的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参... 目录一、引言二、元数据的概念2.1 定义2.2 作用三、Java 注解的基础3.1 注解的定义3.2 内

Java中使用Java Mail实现邮件服务功能示例

《Java中使用JavaMail实现邮件服务功能示例》:本文主要介绍Java中使用JavaMail实现邮件服务功能的相关资料,文章还提供了一个发送邮件的示例代码,包括创建参数类、邮件类和执行结... 目录前言一、历史背景二编程、pom依赖三、API说明(一)Session (会话)(二)Message编程客

Java中List转Map的几种具体实现方式和特点

《Java中List转Map的几种具体实现方式和特点》:本文主要介绍几种常用的List转Map的方式,包括使用for循环遍历、Java8StreamAPI、ApacheCommonsCollect... 目录前言1、使用for循环遍历:2、Java8 Stream API:3、Apache Commons

JavaScript中的isTrusted属性及其应用场景详解

《JavaScript中的isTrusted属性及其应用场景详解》在现代Web开发中,JavaScript是构建交互式应用的核心语言,随着前端技术的不断发展,开发者需要处理越来越多的复杂场景,例如事件... 目录引言一、问题背景二、isTrusted 属性的来源与作用1. isTrusted 的定义2. 为

Java循环创建对象内存溢出的解决方法

《Java循环创建对象内存溢出的解决方法》在Java中,如果在循环中不当地创建大量对象而不及时释放内存,很容易导致内存溢出(OutOfMemoryError),所以本文给大家介绍了Java循环创建对象... 目录问题1. 解决方案2. 示例代码2.1 原始版本(可能导致内存溢出)2.2 修改后的版本问题在

Java CompletableFuture如何实现超时功能

《JavaCompletableFuture如何实现超时功能》:本文主要介绍实现超时功能的基本思路以及CompletableFuture(之后简称CF)是如何通过代码实现超时功能的,需要的... 目录基本思路CompletableFuture 的实现1. 基本实现流程2. 静态条件分析3. 内存泄露 bug

Java中Object类的常用方法小结

《Java中Object类的常用方法小结》JavaObject类是所有类的父类,位于java.lang包中,本文为大家整理了一些Object类的常用方法,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. public boolean equals(Object obj)2. public int ha

SpringBoot项目中Maven剔除无用Jar引用的最佳实践

《SpringBoot项目中Maven剔除无用Jar引用的最佳实践》在SpringBoot项目开发中,Maven是最常用的构建工具之一,通过Maven,我们可以轻松地管理项目所需的依赖,而,... 目录1、引言2、Maven 依赖管理的基础概念2.1 什么是 Maven 依赖2.2 Maven 的依赖传递机

SpringBoot实现动态插拔的AOP的完整案例

《SpringBoot实现动态插拔的AOP的完整案例》在现代软件开发中,面向切面编程(AOP)是一种非常重要的技术,能够有效实现日志记录、安全控制、性能监控等横切关注点的分离,在传统的AOP实现中,切... 目录引言一、AOP 概述1.1 什么是 AOP1.2 AOP 的典型应用场景1.3 为什么需要动态插