Spring自带定时任务@Scheduled注解实例讲解

 更新时间:2024年06月06日 10:13:41   作者:心流时间  
这篇文章主要介绍了Spring自带定时任务@Scheduled注解的相关知识,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧

Java技术迷

1. cron表达式生成器

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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

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

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

3.3 fixedRate

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

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

3.4 initialDelay

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

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

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

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

3.6 timeUnit

时间单位,默认毫秒

1
TimeUnit timeUnit() default TimeUnit.MILLISECONDS;

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

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

@Scheduled注解加载的过程,以及它是如何执行的:

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

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

4.3 执行任务。

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

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

然后,在ThreadPoolTaskScheduler类中,会对线程池进行初始化,线程池的核心线程数量为1,

1
private volatile int poolSize = 1;

阻塞队列为DelayedWorkQueue。

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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);
    }
}

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

想要避免顺序执行,进行并发,就要配置定时任务线程池:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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 {
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.setScheduler(getExecutor());
    }
    @Bean
    public Executor getExecutor(){
        return new ScheduledThreadPoolExecutor(5);
    }
}

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

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

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

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

原因:

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

解决办法:

重启项目

不使用@Scheduled注解,改成ScheduledThreadPoolExecutor进行替代,部分代码:

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

参考资料:

参考1
参考2
参考3

到此这篇关于Spring自带定时任务@Scheduled注解的文章就介绍到这了,更多相关Spring定时任务@Scheduled注解内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

蓄力AI

微信公众号搜索 “ 脚本之家 ” ,选择关注

程序猿的那些事、送书等活动等着你

原文链接:https://blog.csdn.net/weixin_43024834/article/details/139427509

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权/违法违规/事实不符,请将相关资料发送至 reterry123@163.com 进行投诉反馈,一经查实,立即处理!

相关文章

  • SpringBoot引入模板引擎实现视图解析

    SpringBoot引入模板引擎实现视图解析

    这篇文章主要介绍了SpringBoot引入模板引擎实现视图解析方法流程,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧
    2022-10-10
  • 全面理解java中的异常处理机制

    全面理解java中的异常处理机制

    下面小编就为大家带来一篇全面理解java中的异常处理机制。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-06-06
  • Java中对null进行强制类型转换的方法

    Java中对null进行强制类型转换的方法

    小编对null进行强转会不会抛错,非常的好奇,下面小编通过实例代码给大家介绍Java中对null进行强制类型转换的方法,感兴趣的朋友参考下吧
    2018-09-09
  • Java多线程工具CompletableFuture详解

    Java多线程工具CompletableFuture详解

    这篇文章主要介绍了Java多线程工具CompletableFuture详解,CompletableFuture 是 java 1.8 追加的新特性,通俗的话来说,是一个函数式的,用于控制多任务同步、异步组合操作的工具,需要的朋友可以参考下
    2024-01-01
  • Java中StringBuilder字符串类型的操作方法及API整理

    Java中StringBuilder字符串类型的操作方法及API整理

    Java中的StringBuffer类继承于AbstractStringBuilder,用来创建非线程安全的字符串类型对象,下面即是对Java中StringBuilder字符串类型的操作方法及API整理
    2016-05-05
  • 使用Spring自定义实现IOC和依赖注入(注解方式)

    使用Spring自定义实现IOC和依赖注入(注解方式)

    这篇文章主要介绍了使用Spring自定义实现IOC和依赖注入(注解方式),具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • 深入理解JSON及其在Java中的应用小结

    深入理解JSON及其在Java中的应用小结

    json它是一种轻量级的数据交换格式,由于其易于阅读和编写,同时也易于机器解析和生成,因此广泛应用于网络数据交换和配置文件,这篇文章主要介绍了深入理解JSON及其在Java中的应用,需要的朋友可以参考下
    2023-12-12
  • java操作XML实例代码

    java操作XML实例代码

    这篇文章主要介绍了java操作XML实例代码,有需要的朋友可以参考一下
    2014-01-01
  • 详解Java8与Runtime.getRuntime().availableProcessors()

    详解Java8与Runtime.getRuntime().availableProcessors()

    这篇文章主要介绍了详解Java8与Runtime.getRuntime().availableProcessors(),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-06-06
  • Springboot Thymeleaf字符串对象实例解析

    Springboot Thymeleaf字符串对象实例解析

    这篇文章主要介绍了Springboot Thymeleaf字符串对象实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2007-09-09

最新评论