理解Spring定时任务的fixedRate和fixedDelay

 
  •  
  • // cron expression 自定义规则

  • // 参数顺序

  • // 秒 分 时 日 月 星期

  • // "0 0 * * * *" = the top of every hour of every day.

  • // "*/10 * * * * *" = every ten seconds.

  • // "0 0 8-10 * * *" = 8, 9 and 10 o'clock of every day.

  • // "0 0 6,19 * * *" = 6:00 AM and 7:00 PM every day.

  • // "0 0/30 8-10 * * *" = 8:00, 8:30, 9:00, 9:30, 10:00 and 10:30 every day.

  • // "0 0 9-17 * * MON-FRI" = on the hour nine-to-five weekdays

  • // "0 0 0 25 12 ?" = every Christmas Day at midnight

  •  
  • /**

  • * 10秒执行一次

  • */

  • @Scheduled(cron="0/10 * * * * *")

  • -----------------------------------------------------------------------------------------------

 

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

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

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

public abstract long fixedRate 
Execute the annotated method with a fixed period in milliseconds between invocations.

public abstract long fixedDelay 
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 线程池中执行,而是每次创建新线程来执行。

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

复制代码

@EnableScheduling
@SpringBootApplication
public class Application {
 
    private AtomicInteger number = new AtomicInteger();
 
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
 
    @Bean
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        taskScheduler.setPoolSize(5);
        return taskScheduler;
    }
 
    @Scheduled(fixedRate = 5000)
    public void job() {
        LocalTime start = LocalTime.now();
        System.out.println(Thread.currentThread() + " start " + number.incrementAndGet() + " @ "  + start);
        try {
            Thread.sleep(ThreadLocalRandom.current().nextInt(15) * 1000);
        } catch (InterruptedException e) {
        }
        LocalTime end = LocalTime.now();
        System.out.println(Thread.currentThread() + " end " + number.get() + " @ " + end
            + ", seconds cost " + (ChronoUnit.SECONDS.between(start, end)));
    }
}

复制代码

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

复制代码

Thread[taskScheduler-1,5,main] start 1 @ 01:23:11.726
Thread[taskScheduler-1,5,main] end 1 @ 01:23:24.732, seconds cost 13
Thread[taskScheduler-1,5,main] start 2 @ 01:23:24.736
Thread[taskScheduler-1,5,main] end 2 @ 01:23:28.737, seconds cost 4
Thread[taskScheduler-2,5,main] start 3 @ 01:23:28.738
Thread[taskScheduler-2,5,main] end 3 @ 01:23:40.739, seconds cost 12
Thread[taskScheduler-1,5,main] start 4 @ 01:23:40.740
Thread[taskScheduler-1,5,main] end 4 @ 01:23:52.745, seconds cost 12
Thread[taskScheduler-3,5,main] start 5 @ 01:23:52.745
Thread[taskScheduler-3,5,main] end 5 @ 01:24:00.748, seconds cost 8
Thread[taskScheduler-3,5,main] start 6 @ 01:24:00.749
Thread[taskScheduler-3,5,main] end 6 @ 01:24:05.750, seconds cost 5
Thread[taskScheduler-3,5,main] start 7 @ 01:24:05.750
Thread[taskScheduler-3,5,main] end 7 @ 01:24:05.750, seconds cost 0
Thread[taskScheduler-3,5,main] start 8 @ 01:24:05.750
Thread[taskScheduler-3,5,main] end 8 @ 01:24:14.752, seconds cost 9
Thread[taskScheduler-3,5,main] start 9 @ 01:24:14.752
Thread[taskScheduler-3,5,main] end 9 @ 01:24:26.756, seconds cost 12
Thread[taskScheduler-3,5,main] start 10 @ 01:24:26.757
Thread[taskScheduler-3,5,main] end 10 @ 01:24:39.757, seconds cost 13
Thread[taskScheduler-3,5,main] start 11 @ 01:24:39.757
Thread[taskScheduler-3,5,main] end 11 @ 01:24:43.761, seconds cost 4
Thread[taskScheduler-3,5,main] start 12 @ 01:24:43.762
Thread[taskScheduler-3,5,main] end 12 @ 01:24:47.763, seconds cost 4
Thread[taskScheduler-3,5,main] start 13 @ 01:24:47.763
Thread[taskScheduler-3,5,main] end 13 @ 01:24:49.766, seconds cost 2
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:16T2T1 执行后时间来到了 01:23:24, 下一次任务 T2 安排在更早的时间,所以立即执行 T2
01:23:21T3T2 完后时间是 01:23:28, T3 的安排时间也比它早,所以也是立即执行 T3
01:23:26T4T3 完后时间是 01:23:40, 无需等待立即执行 T4
01:23:31T5

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

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

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

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

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

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

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

复制代码

 private AtomicBoolean firstTime = new AtomicBoolean(true);
 
    @Scheduled(fixedRate = 5000)
    public void job() {
        LocalTime start = LocalTime.now();
        System.out.println(Thread.currentThread() + " start " + number.incrementAndGet() + " @ "  + start);
        if (firstTime.getAndSet(false)) {
            try {
                Thread.sleep(23000);
            } catch (InterruptedException e) {
            }
        }
        LocalTime end = LocalTime.now();
        System.out.println(Thread.currentThread() + " end " + number.get() + " @ " + end
            + ", seconds cost " + (ChronoUnit.SECONDS.between(start, end)));
    }

复制代码

  输出为

复制代码

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

复制代码

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

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

 

Thread[taskScheduler-1,5,main] start 1 @ 02:54:33.750
Thread[taskScheduler-1,5,main] end 1 @ 02:54:43.756, seconds cost 10
Thread[taskScheduler-1,5,main] start 2 @ 02:54:48.765
Thread[taskScheduler-1,5,main] end 2 @ 02:55:00.767, seconds cost 12
Thread[taskScheduler-2,5,main] start 3 @ 02:55:05.769
Thread[taskScheduler-2,5,main] end 3 @ 02:55:11.772, seconds cost 6
Thread[taskScheduler-1,5,main] start 4 @ 02:55:16.775
Thread[taskScheduler-1,5,main] end 4 @ 02:55:21.781, seconds cost 5
Thread[taskScheduler-3,5,main] start 5 @ 02:55:26.785
Thread[taskScheduler-3,5,main] end 5 @ 02:55:27.787, seconds cost 1
Thread[taskScheduler-3,5,main] start 6 @ 02:55:32.789
Thread[taskScheduler-3,5,main] end 6 @ 02:55:41.792, seconds cost 9
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

 

相关推荐
SystemVerilog听课学习笔记,包括讲义截取、知识点记录、注意事项等细节标注。 目录如下: 第一章 SV环境构建常识 1 1.1 数据类型 1 四、二值逻辑 4 定宽数组 9 foreach 13 动态数组 16 队列 19 关联数组 21 枚举类型 23 字符串 25 1.2 过程块方法 27 initialalways 30 function逻辑电路 33 task时序电路 35 动态 静态变量 39 1.3 设计例化连接 45 第二章 验证方法 393 动态仿真 395 静态检查 397 虚拟模型 403 硬件加速 405 效能验证 408 性能验证 410 第三章 SV组件实现 99 3.1 接口 100 什么是interface 101 接口优势 108 3.2 采样数据驱动 112 竞争问题 113 接口中时序块clocking 123 利于clocking驱动 133 3.3 测试开始结束 136 仿真开始 139 program隐式结束 143 program显式结束 145 软件域program 147 3.4 调试方法 150 第四章 验证计划 166 4.1 计划概述 166 4.2 计划内容 173 4.3 计划实现 185 4.4 计划进程评估 194 第五章 验证管理 277 6.1 验证周期检查 277 6.2 管理三要素 291 6.3 验证收敛 303 6.4 问题追踪 314 6.5 团队建设 321 6.6 验证专业化 330 第六章 验证平台结构 48 2.1 测试平台 49 2.2 硬件设计描述 55 MCDF接口描述 58 MCDF接口时序 62 MCDF寄存器描述 65 2.3 激励发生器 67 channel initiator 72 register initiator 73 2.4 监测器 74 2.5 比较器 81 2.6 验证结构 95 第七章 激励发生封装:类 209 5.1 概述 209 5.2 类成员 233 5.3 类继承 245 三种类型权限 protected/local/public 247 this super 253 成员覆盖 257 5.4 句柄使用 263 5.5 包使用 269 第八章 激励发生随机化 340 7.1 随机约束分布 340 权重分布 353 条件约束 355 7.2 约束块控制 358 7.3 随机函数 366 7.4 数组约束 373 7.5 随机控制 388 第九章 线程与通信 432 9.1 线程使用 432 9.2 线程控制 441 三个fork...join 443 等待衍生线程 451 停止线程disable 451 9.3 线程通信 458 第十章 进程评估:覆盖率 495 10.1 覆盖率类型 495 10.2 功能覆盖策略 510 10.3 覆盖组 516 10.4 数据采样 524 10.5 覆盖选项 544 10.6 数据分析 550 第十一章 SV语言核心进阶 552 11.1 类型转换 552 11.2 虚方法 564 11.3 对象拷贝 575 11.4 回调函数 584 11.5 参数化类 590 第十二章 UVM简介 392 8.2 UVM简介 414 8.3 UVM组件 420 8.4 UVM环境 425
<p> 课程演示环境:<span>Ubuntu</span> </p> <p> <span> </span> </p> <p> 需要学习<span>Windows</span>系统<span>YOLOv4-tiny</span>同学请前往《<span>Windows</span>版<span>YOLOv4-tiny</span>目标检测实战:训练自己数据集》 <span></span> </p> <p> <span> </span> </p> <p> <span style="color:#E53333;">YOLOv4-tiny</span><span style="color:#E53333;">来了!速度大幅提升!</span><span></span> </p> <p> <span> </span> </p> <p> <span>YOLOv4-tiny</span>在<span>COCO</span>上性能可达到:<span>40.2% AP50, 371 FPS (GTX 1080 Ti)</span>。相较于<span>YOLOv3-tiny</span>,<span>AP</span><span>FPS</span>性能有巨大提升。并且,<span>YOLOv4-tiny</span>权重文件只有<span>23MB</span>,适合在移动端、嵌入式设备、边缘计算等设备上部署。<span></span> </p> <p> <span> </span> </p> <p> 本课程将手把手地教大家使用<span>labelImg</span>标注使用<span>YOLOv4-tiny</span>训练自己数据集。课程实战分为两个项目:单目标检测(足球目标检测)多目标检测(足球梅西同时检测)。<span></span> </p> <p> <span> </span> </p> <p> 本课程<span>YOLOv4-tiny</span>使用<span>AlexAB/darknet</span>,在<span>Ubuntu</span>系统上做项目演示。包括:<span>YOLOv4-tiny</span>网络结构、安装<span>YOLOv4-tiny</span>、标注自己数据集、整理自己数据集、修改配置文件、训练自己数据集、测试训练出网络模型、性能统计<span>(mAP</span>计算画出<span>PR</span>曲线<span>)</span>先验框聚类分析。 <span> </span> </p> <p> <span> </span> </p> <p> 除本课程《<span>YOLOv4-tiny</span>目标检测实战:训练自己数据集》外,本人推出了有关<span>YOLOv4</span>目标检测系列课程。请持续关注该系列其它视频课程,包括:<span></span> </p> <p> 《<span>YOLOv4</span>目标检测实战:训练自己数据集》<span></span> </p> <p> 《<span>YOLOv4</span>目标检测实战:人脸口罩佩戴识别》<span></span> </p> <p> 《<span>YOLOv4</span>目标检测实战:中国交通标志识别》<span></span> </p> <p> 《<span>YOLOv4</span>目标检测:原理与源码解析》<span></span> </p> <p> <br /> </p> <p> <br /> </p> <p> <img alt="" src="https://img-bss.csdnimg.cn/202007061437441198.jpg" /> </p> <img alt="" src="https://img-bss.csdnimg.cn/202007061438066851.jpg" />
<p> <b><span style="background-color:#FFE500;">【超实用课程内容】</span></b> </p> <p> <br /> </p> <p> <br /> </p> <p> 本课程内容包含讲解<span>解读Nginx基础知识,</span><span>解读Nginx核心知识、带领学员进行</span>高并发环境下Nginx性能优化实战,让学生能够快速将所学融合到企业应用中。 </p> <p> <br /> </p> <p style="font-family:Helvetica;color:#3A4151;font-size:14px;background-color:#FFFFFF;"> <b><br /> </b> </p> <p style="font-family:Helvetica;color:#3A4151;font-size:14px;background-color:#FFFFFF;"> <b><span style="background-color:#FFE500;">【课程如何观看?】</span></b> </p> <p style="font-family:Helvetica;color:#3A4151;font-size:14px;background-color:#FFFFFF;"> PC端:<a href="https://edu.csdn.net/course/detail/26277"><span id="__kindeditor_bookmark_start_21__"></span></a><a href="https://edu.csdn.net/course/detail/27216">https://edu.csdn.net/course/detail/27216</a> </p> <p style="font-family:Helvetica;color:#3A4151;font-size:14px;background-color:#FFFFFF;"> 移动端:CSDN 学院APP(注意不是CSDN APP哦) </p> <p style="font-family:Helvetica;color:#3A4151;font-size:14px;background-color:#FFFFFF;"> 本课程为录播课,课程永久有效观看时长,大家可以抓紧时间学习后一起讨论哦~ </p> <p style="font-family:"color:#3A4151;font-size:14px;background-color:#FFFFFF;"> <br /> </p> <p class="ql-long-24357476" style="font-family:"color:#3A4151;font-size:14px;background-color:#FFFFFF;"> <strong><span style="background-color:#FFE500;">【学员专享增值服务】</span></strong> </p> <p class="ql-long-24357476" style="font-family:"color:#3A4151;font-size:14px;background-color:#FFFFFF;"> <b>源码开放</b> </p> <p class="ql-long-24357476" style="font-family:"color:#3A4151;font-size:14px;background-color:#FFFFFF;"> 课件、课程案例代码完全开放给你,你可以根据所学知识,自行修改、优化 </p> <p class="ql-long-24357476" style="font-family:"color:#3A4151;font-size:14px;background-color:#FFFFFF;"> 下载方式:电脑登录<a href="https://edu.csdn.net/course/detail/26277"></a><a href="https://edu.csdn.net/course/detail/27216">https://edu.csdn.net/course/detail/27216</a>,播放页面右侧点击课件进行资料打包下载 </p> <p> <br /> </p> <p> <br /> </p> <p> <br /> </p>
©️2020 CSDN 皮肤主题: 撸撸猫 设计师:马嘣嘣 返回首页