用过  Spring 的 @EnableScheduling 的都知道,我们用三种形式来部署计划任务,即 @Scheduled 注解的 fixedRate(fixedRateString), fixedDelay(fixedDelayString), 以及 cron. cron 不在这里讨论的范畴。

  我们着重在如何理解 fixedRate 和 fixedDelay 的区别。

  在 Spring 的  Scheduled 注解的 JavaDoc 对此的解释很简单

  1. public abstract long fixedRate
  2. Execute the annotated method with a fixed period in milliseconds between invocations.
  3.  
  4. public abstract long fixedDelay
  5. Execute the annotated method with a fixed period in milliseconds between the end of the last invocation and the start of the next.

  只是说是 fixedRate 任务两次执行时间间隔是任务的开始点,而 fixedDelay 的间隔是前次任务的结束与下次任务的开始。

  大致用示意字符串来表示如下(每个 T1, 或 T2 代表任务执行秒数(每次任务执行时间不定),假定 fixedRate 或  fixedDelay 的值是 5 秒,用 W 表示等待的数)

  fixedRate:    T1.T1WWWT2.T2.T2WW.T3.T3.T3.T3.T3.T4.T4.T4.T4.T4.T4.T4T5T5WWWT6.T6........

  fixedDelay:  T1.T1.WWWWW.T2.T2.T2WWWWW.T3.T3.T3.T3.T3.WWWWW.T4.T4.T4.T4.T4.T4.T4.WWWWWT6.T6......

  一般来说能理解到上面两个场景已经差不多了,相比而言 fixedDelay 简单些,盯着上一次任务的屁股就行。

  以前我对 fixedRate 还有一个误区就是,以为任务时长超过 fixedRate 时会启动多个任务实例,其实不会; 只不过会在上次任务执行完后立即启动下一轮。除非这个 Job 方法用 @Async 注解了,使得任务不在 TaskScheduler 线程池中执行,而是每次创建新线程来执行。

  具体理解我们可以用代码来演示

  1. @EnableScheduling
  2. @SpringBootApplication
  3. public class Application {
  4.  
  5. private AtomicInteger number = new AtomicInteger();
  6.  
  7. public static void main(String[] args) {
  8. SpringApplication.run(Application.class, args);
  9. }
  10.  
  11. @Bean
  12. public TaskScheduler taskScheduler() {
  13. ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
  14. taskScheduler.setPoolSize(5);
  15. return taskScheduler;
  16. }
  17.  
  18. @Scheduled(fixedRate = 5000)
  19. public void job() {
  20. LocalTime start = LocalTime.now();
  21. System.out.println(Thread.currentThread() + " start " + number.incrementAndGet() + " @ " + start);
  22. try {
  23. Thread.sleep(ThreadLocalRandom.current().nextInt(15) * 1000);
  24. } catch (InterruptedException e) {
  25. }
  26. LocalTime end = LocalTime.now();
  27. System.out.println(Thread.currentThread() + " end " + number.get() + " @ " + end
  28. + ", seconds cost " + (ChronoUnit.SECONDS.between(start, end)));
  29. }
  30. }

  初始化了一个线程池大小为 5  的 TaskScheduler, 避免了所有任务都用一个线程来执行。 上例中的 fixedRate 为 5 秒,任务执行时间在 0 ~ 15 秒之间,先来看一组数据(样本数据越多越生动)

  1. Thread[taskScheduler-1,5,main] start 1 @ 01:23:11.726
  2. Thread[taskScheduler-1,5,main] end 1 @ 01:23:24.732, seconds cost 13
  3. Thread[taskScheduler-1,5,main] start 2 @ 01:23:24.736
  4. Thread[taskScheduler-1,5,main] end 2 @ 01:23:28.737, seconds cost 4
  5. Thread[taskScheduler-2,5,main] start 3 @ 01:23:28.738
  6. Thread[taskScheduler-2,5,main] end 3 @ 01:23:40.739, seconds cost 12
  7. Thread[taskScheduler-1,5,main] start 4 @ 01:23:40.740
  8. Thread[taskScheduler-1,5,main] end 4 @ 01:23:52.745, seconds cost 12
  9. Thread[taskScheduler-3,5,main] start 5 @ 01:23:52.745
  10. Thread[taskScheduler-3,5,main] end 5 @ 01:24:00.748, seconds cost 8
  11. Thread[taskScheduler-3,5,main] start 6 @ 01:24:00.749
  12. Thread[taskScheduler-3,5,main] end 6 @ 01:24:05.750, seconds cost 5
  13. Thread[taskScheduler-3,5,main] start 7 @ 01:24:05.750
  14. Thread[taskScheduler-3,5,main] end 7 @ 01:24:05.750, seconds cost 0
  15. Thread[taskScheduler-3,5,main] start 8 @ 01:24:05.750
  16. Thread[taskScheduler-3,5,main] end 8 @ 01:24:14.752, seconds cost 9
  17. Thread[taskScheduler-3,5,main] start 9 @ 01:24:14.752
  18. Thread[taskScheduler-3,5,main] end 9 @ 01:24:26.756, seconds cost 12
  19. Thread[taskScheduler-3,5,main] start 10 @ 01:24:26.757
  20. Thread[taskScheduler-3,5,main] end 10 @ 01:24:39.757, seconds cost 13
  21. Thread[taskScheduler-3,5,main] start 11 @ 01:24:39.757
  22. Thread[taskScheduler-3,5,main] end 11 @ 01:24:43.761, seconds cost 4
  23. Thread[taskScheduler-3,5,main] start 12 @ 01:24:43.762
  24. Thread[taskScheduler-3,5,main] end 12 @ 01:24:47.763, seconds cost 4
  25. Thread[taskScheduler-3,5,main] start 13 @ 01:24:47.763
  26. Thread[taskScheduler-3,5,main] end 13 @ 01:24:49.766, seconds cost 2
  27. Thread[taskScheduler-3,5,main] start 14 @ 01:24:49.767

把 start 行用红色显示。

  1. 任务 1 与 2 之间间隔时间是任务时长 13,所以任务 2 在 1 结束后立即启动
  2. 任务 3 与 2 之间间隔还不到 5 秒,也是在任务 2 结束后立即执行
  3. 后面都是在上次任务结束后立即执行下一次任务,看到 7 与 8 之间相差 0 秒,13 与 14 之间相关 2 秒

从上面的结果分析,似乎 fixedRate 越到后面都不起作用,总是任务一个接一个的执行。也就是说上面 fixedRate 的示意串

T1.T1WWWT2.T2.T2WW.T3.T3.T3.T3.T3.T4.T4.T4.T4.T4.T4.T4T5T5WWWT6.T6........

已经不成立了,当中间发生了一长时间的任务后,fixedRate 变成了如下的形式

T1.T1.WWWT2.T2.T2.T2.T2.T2.T2.T2.T2.T2.T2.T2.T3.T3.T3.T3.T4.T4.T4.T5.T5.T5.......

  任务间的等待都被抹除掉了,这是为什么呢?因为 fixedRate 会对将要执行的任务作一个预先编排,由上输出可以第一次任务在 01:23:11 时间点启动,所以  fixedRate 会基于此把一个时间表准备好,如下

01:23:16 T2 T1 执行后时间来到了 01:23:24, 下一次任务 T2 安排在更早的时间,所以立即执行 T2
01:23:21 T3 T2 完后时间是 01:23:28, T3 的安排时间也比它早,所以也是立即执行 T3
01:23:26 T4 T3 完后时间是 01:23:40, 无需等待立即执行 T4
01:23:31 T5

后面的情况都是一样的, T5.endTime > T6.scheduledTime + fixedRate, 所以立即执行 T6

除非有一些短任务能把时间压缩回去,造成上一次任务结束后需要进行等待

01:23:35 T6
01:23:41 T7

  因此,fixedRate 总是在上一次任务结束后从时间表中挑出下一次任务,对比该任务所预先排好的时间是否晚于上次任务启动时间加上 fixedRate 值,是则等待到预定的时间,否则立即执行。

  假设 T1 执行完后时间是 T1.endTime, 这时候判断 T1.endTime < T2.scheduledTime + fixedRate,  是则等待到 T2.scheduledTime 启动 T2, 否则立即执行  T2

  我们可以用代码进一步来验证上面的说法,其实最具说服力的莫过于源代码,这里只提供感观体验

  代码的改动是第一次任务执行时间为 23  秒,此后的任务是不耗时的空操作

  1. private AtomicBoolean firstTime = new AtomicBoolean(true);
  2.  
  3. @Scheduled(fixedRate = 5000)
  4. public void job() {
  5. LocalTime start = LocalTime.now();
  6. System.out.println(Thread.currentThread() + " start " + number.incrementAndGet() + " @ " + start);
  7. if (firstTime.getAndSet(false)) {
  8. try {
  9. Thread.sleep(23000);
  10. } catch (InterruptedException e) {
  11. }
  12. }
  13. LocalTime end = LocalTime.now();
  14. System.out.println(Thread.currentThread() + " end " + number.get() + " @ " + end
  15. + ", seconds cost " + (ChronoUnit.SECONDS.between(start, end)));
  16. }

  输出为

  1. Thread[taskScheduler-1,5,main] start 1 @ 03:27:54.556
  2. Thread[taskScheduler-1,5,main] end 1 @ 03:28:17.562, seconds cost 23
  3. Thread[taskScheduler-1,5,main] start 2 @ 03:28:17.566
  4. Thread[taskScheduler-1,5,main] end 2 @ 03:28:17.566, seconds cost 0
  5. Thread[taskScheduler-2,5,main] start 3 @ 03:28:17.566
  6. Thread[taskScheduler-2,5,main] end 3 @ 03:28:17.567, seconds cost 0
  7. Thread[taskScheduler-1,5,main] start 4 @ 03:28:17.584
  8. Thread[taskScheduler-1,5,main] end 4 @ 03:28:17.584, seconds cost 0
  9. Thread[taskScheduler-4,5,main] start 5 @ 03:28:17.584
  10. Thread[taskScheduler-4,5,main] end 5 @ 03:28:17.584, seconds cost 0
  11. Thread[taskScheduler-4,5,main] start 6 @ 03:28:19.549
  12. Thread[taskScheduler-4,5,main] end 6 @ 03:28:19.550, seconds cost 0
  13. Thread[taskScheduler-4,5,main] start 7 @ 03:28:24.549
  14. Thread[taskScheduler-4,5,main] end 7 @ 03:28:24.550, seconds cost 0
  15. Thread[taskScheduler-4,5,main] start 8 @ 03:28:29.548
  16. Thread[taskScheduler-4,5,main] end 8 @ 03:28:29.549, seconds cost 0
  17. Thread[taskScheduler-4,5,main] start 9 @ 03:28:34.546

  因为第一次任务 23 秒的延误,所以后续的任务 2, 3, 4, 5 都是上次任务(耗时为 0)完后立即执行,任务 6 把 2 秒的差距找回来了,以后都是每隔 5 秒执行一次。

  fixedDelay 的逻辑就相当简单了,基本无需用代码来演示。不妨把上面的代码中的 fixedRate 改成 fixedDelay 来一见分晓:

  1. Thread[taskScheduler-1,5,main] start 1 @ 02:54:33.750
  2. Thread[taskScheduler-1,5,main] end 1 @ 02:54:43.756, seconds cost 10
  3. Thread[taskScheduler-1,5,main] start 2 @ 02:54:48.765
  4. Thread[taskScheduler-1,5,main] end 2 @ 02:55:00.767, seconds cost 12
  5. Thread[taskScheduler-2,5,main] start 3 @ 02:55:05.769
  6. Thread[taskScheduler-2,5,main] end 3 @ 02:55:11.772, seconds cost 6
  7. Thread[taskScheduler-1,5,main] start 4 @ 02:55:16.775
  8. Thread[taskScheduler-1,5,main] end 4 @ 02:55:21.781, seconds cost 5
  9. Thread[taskScheduler-3,5,main] start 5 @ 02:55:26.785
  10. Thread[taskScheduler-3,5,main] end 5 @ 02:55:27.787, seconds cost 1
  11. Thread[taskScheduler-3,5,main] start 6 @ 02:55:32.789
  12. Thread[taskScheduler-3,5,main] end 6 @ 02:55:41.792, seconds cost 9
  13. Thread[taskScheduler-3,5,main] start 7 @ 02:55:46.794

  总是上次任务结束 5 秒后,由此可见 fixedDelay 不存在任务的预先编排操作了,都是相机而为。

  最后小结一下:fixedRate 每次任务结束后会从任务编排表中找下一次该执行的任务,判断是否到时机执行。fixedRate 的任务某次执行时间再长也不会造成两次任务实例同时执行,除非用了 @Async 注解。 fixedDelay 总是前一次任务完成后,延时固定长度然后执行一次任务

  本文来自于: https://unmi.cc/understand-spring-schedule-fixedrate-fixeddelay/, 来自 隔叶黄莺 Unmi Blog

理解Spring定时任务的fixedRate和fixedDelay的更多相关文章

  1. 理解 Spring 定时任务的 fixedRate 和 fixedDelay 的区别

    用过 Spring 的 @EnableScheduling 的都知道,有三种方式,即 @Scheduled 注解的 fixedRate(fixedRateString), fixedDelay(fix ...

  2. 理解Spring定时任务@Scheduled的两个属性fixedRate和fixedDelay

    fixedRate和fixedDelay都是表示任务执行的间隔时间 fixedRate和fixedDelay的区别:fixedDelay非常好理解,它的间隔时间是根据上次的任务结束的时候开始计时的.比 ...

  3. spring 定时任务配置

    1.(易)如何在spring中配置定时任务? spring的定时任务配置分为三个步骤: 1.定义任务 2.任务执行策略配置 3.启动任务 (程序中一般我们都是到过写的,直观些) 1.定义任务 < ...

  4. 关于Spring定时任务(定时器)用法

    Spring定时任务的几种实现 Spring定时任务的几种实现 一.分类 从实现的技术上来分类,目前主要有三种技术(或者说有三种产品): 从作业类的继承方式来讲,可以分为两类: 从任务调度的触发时机来 ...

  5. (3)Spring定时任务的几种实现

    Spring定时任务的几种实现 近日项目开发中需要执行一些定时任务,比如需要在每天凌晨时候,分析一次前一天的日志信息,借此机会整理了一下定时任务的几种实现方式,由于项目采用spring框架,所以我都将 ...

  6. spring定时任务的几种实现方式

    Spring定时任务的几种实现 近日项目开发中需要执行一些定时任务,比如需要在每天凌晨时候,分析一次前一天的日志信息,借此机会整理了一下定时任务的几种实现方式,由于项目采用spring框架,所以我都将 ...

  7. (转)Spring定时任务的几种实现

    Spring定时任务的几种实现 博客分类: spring框架 quartzspringspring-task定时任务注解  Spring定时任务的几种实现 近日项目开发中需要执行一些定时任务,比如需要 ...

  8. Spring定时任务@Scheduled注解使用方式

    1.开篇 spring的@Scheduled定时任务相信大家都是十分熟悉.最近在使用过程中发现了一些问题,写篇文章,和大家分享一下.结论在最后,不想看冗长过程的小伙伴可以直接拉到最后看结论. 2.简单 ...

  9. spring定时任务注解@Scheduled的记录

    spring 定时任务@Scheduled 转自https://www.cnblogs.com/0201zcr/p/5995779.html 1.配置文件 <?xml version=" ...

随机推荐

  1. UniGUI的TUniLoginForm窗口自定义背景色

    uniGUI的TUniLoginForm类创建的登录窗口默认是不带颜色,可以自定义css风格来改变背景颜色. 一般是通过在UniServerModule中,在CustcomSS属性中,修改extjs的 ...

  2. LeetCode142:Linked List Cycle II

    题目: Given a linked list, return the node where the cycle begins. If there is no cycle, return null. ...

  3. 使用.NET Core 2.1的Azure WebJobs

    WebJobs不是Azure和.NET中的新事物. Visual Studio 2017中甚至还有一个默认的Azure WebJob模板,用于完整的.NET Framework. 但是,Visual ...

  4. 微信小程序开发教程(一)—介绍和准备

    前言: 因为客户需要,也为了更好的发展我们公司的产品,所以决定扩展移动端.但是由于公司没有原生安卓开发人员,而且开发安卓成本比较高,所以公司一致决定开发微信小程序,也是由于微信小程序最近的火热. 后台 ...

  5. linux中权限

    $ ls -l /bin/bash -rwxr-xr-x 1 root wheel 430540 Dec 23 18:27 /bin/bash -rwxr-xr-x 包含该特殊文件的权限的符号表示.该 ...

  6. Android之系统架构

    Android 是Google开发的基于Linux平台的开源手机操作系统.它包括操作系统.用户界面和应用程序 —— 移动电话工作所需的全部软件,而且不存在任何以往阻碍移动产业创新的专有权障碍.Andr ...

  7. ubuntu14.04 安装五笔输入法(fcitx)

    ubuntu 14.04安装完成之后,一打字,默认的ibus一直在显示.解决办法,直接卸载ibus,使用fcitx. fictix拼音有fcitx-pinyin.fcitx-sogoupinyin.f ...

  8. SQL实现数据行列转换

    前言: 在日常的工作中,使用数据库查看数据是很经常的事,数据库的数据非常多,如果此时的数据设计是一行行的设计话,就会有多行同一个用户的数据,查看起来比较费劲,如果数据较多时,不方便查看,为了更加方便工 ...

  9. ConcurrentHashMap的使用注意事项

    有人说:虽然ConcurrentHashMap是线程安全的,但是在如下的代码中: ConcurrentHashMap<String,String> map; String getStrin ...

  10. Vim实用技巧系列 - 代码注释

    在写代码时候,有时候需要临时注释掉一部分代码,之后还要取消这些注释.真麻烦.那么,用Vim怎么做这件事呢? 第一种方法,块模式.                 再次声明,本系列全部以windows下 ...