热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

SpringBoot学习(十九):SpringBoot做定时任务,原来这么简单

前言SpringBoot系列:点击查看SpringBoot系列文章定时任务SpringBoot定时任务实现在主要有两种方法:第一种:使用spring

前言

Spring Boot系列: 点击查看Spring Boot系列文章



定时任务

Spring Boot定时任务实现在主要有两种方法:
第一种:使用spring自带的Task任务。使用简单,功能比较单一
第二种:使用Quartz框架。功能强大,使用起来较复杂

以下主要介绍第一种方法,第一种方法对应一般普通的定时任务也够用了



Task开发

由于SpringBoot内置了定时任务Scheduled,所以我们不需要额外的导入依赖。

开启定时任务的方法有两种

一种是在配置类上加@EnableScheduling,然后在配置类中写定时任务方法,任务就会定时执行

下面这个是@EnableScheduling源码中的一个例子:

* @Configuration* @EnableScheduling* public class AppConfig {** @Scheduled(fixedRate=1000)* public void work() {* // task execution logic* }* }

还有一种方法是在启动类上加@EnableScheduling注解,然后spring就会扫描该位置下定时任务并执行。现在一般是使用这种方法,但如果定时任务较少的话,使用第一种方法也可以。

@SpringBootApplication
@EnableScheduling
public class DemoApplication {public static void main(String[] args) {SpringApplication.run(DemoApplication.class, args);}}



定义定时任务

下面我以第二种方法为例子,看如何定义定时任务

@Component
public class ScheduledTest {private static final Logger logger = LoggerFactory.getLogger(ScheduledTest.class);@Scheduled(fixedRate = 10000)public void fixedRate() {logger.info("fixedRate开始执行");}@Scheduled(fixedDelay = 10000)public void fixedDelay() {logger.info("fixedDelay开始执行");}@Scheduled(cron = "0/10 * * * * ?")public void cron(){logger.info("cron开始执行");}}

输出:

2020-06-17 14:24:28.626 INFO 18132 --- [ scheduling-1] c.e.d.c.scheduled.ScheduledTest : fixedRate开始执行
2020-06-17 14:24:28.628 INFO 18132 --- [ scheduling-1] c.e.d.c.scheduled.ScheduledTest : fixedDelay开始执行
2020-06-17 14:24:30.001 INFO 18132 --- [ scheduling-1] c.e.d.c.scheduled.ScheduledTest : cron开始执行
2020-06-17 14:24:38.626 INFO 18132 --- [ scheduling-1] c.e.d.c.scheduled.ScheduledTest : fixedRate开始执行
2020-06-17 14:24:38.630 INFO 18132 --- [ scheduling-1] c.e.d.c.scheduled.ScheduledTest : fixedDelay开始执行
2020-06-17 14:24:40.000 INFO 18132 --- [ scheduling-1] c.e.d.c.scheduled.ScheduledTest : cron开始执行

用@Component注解将定时类将给spring管理,这样就能扫描到并执行定时任务。 @Scheduled 注解表示开启一个定时任务,一个@Scheduled 注解的方法就表示了一个定时任务。


@Scheduled注解用来表示定时时间的属性有三个:fixedDelay,fixedRate,cron。

fixedRate 表示任务执行之间的时间间隔,具体是指两次任务的开始时间间隔,即定时任务开始时就开始计时,时间达到fixedRate 设置的时间值,就再次执行任务。

fixedDelay 表示任务执行之间的时间间隔,是从一个定时任务完成之后才开始计时的,达到fixedDelay 设定的值就再次执行,就是指本次任务结束到下次任务开始之间的时间间隔。

fixedRate 和fixedDelay 的区别就是fixedRate 是从一个任务开始执行就开始计时,而fixedDelay 是从一个任务执行完成才开始执行。在一个任务执行较久时,差别就很明显了。
注:fixedRate 和fixedDelay 的时间单位都是毫秒,1秒等于1000毫秒



cron表达式

cron属性对应一个cron表达式

cron表达式有六个或者七个域,每个域表示的时间单位如下:

秒 分钟 小时 日 月 星期 年

注意:在@Scheduled注解中的cron属性采用的是六个域的写法(即没有最后一个表示年的时间域),当输入七个的时候会报表达式错误

下面是一个cron说明表格
在这里插入图片描述

通配符含义:

? 表示不指定值,即不关心某个字段的取值时使用。需要注意的是,月份中的日期和星期可能会起冲突,因此在配置时这两个得有一个是 ?

* 表示所有值,例如:在秒的字段上设置 *,表示每一秒都会触发

, 用来分开多个值,例如在周字段上设置 “MON,WED,FRI” 表示周一,周三和周五触发

- 表示区间,例如在秒上设置 “10-12”,表示 10,11,12秒都会触发
/ 用于递增触发,如在秒上面设置"5/15" 表示从5秒开始,每增15秒触发(5,20,35,50)

#序号(表示每月的第几个周几),例如在周字段上设置"6#3"表示在每月的第三个周六,(用 在母亲节和父亲节再合适不过了)
周字段的设置,若使用英文字母是不区分大小写的 ,即 MON 与mon相同

L 表示最后的意思。在日字段设置上,表示当月的最后一天(依据当前月份,如果是二月还会自动判断是否是润年), 在周字段上表示星期六,相当于"7"或"SAT"(注意周日算是第一天)。如果在"L"前加上数字,则表示该数据的最后一个。例如在周字段上设置"6L"这样的格式,则表示"本月最后一个星期五"

W 表示离指定日期的最近工作日(周一至周五),例如在日字段上设置"15W",表示离每月15号最近的那个工作日触发。如果15号正好是周六,则找最近的周五(14号)触发, 如果15号是周未,则找最近的下周一(16号)触发,如果15号正好在工作日(周一至周五),则就在该天触发。如果指定格式为 “1W”,它则表示每月1号往后最近的工作日触发。如果1号正是周六,则将在3号下周一触发。(注,“W"前只能设置具体的数字,不允许区间”-")
L 和 W 可以一组合使用。如果在日字段上设置"LW",则表示在本月的最后一个工作日触发(一般指发工资 )



cron表达式在线生成网站

推荐一个在线生成cron表达式的网站:https://cron.qqe2.com/
遇到不会写的cron表达式时,进去直接生成就可以了,妈妈再也不用担心不会写cron表达式了



定时任务的坑

Task单线程问题

从上面我的输出结果,可以看到每次的定时任务都是一个线程执行的(scheduling-1),这是因为spring提供的调度器是默认采用单线程的线程池

这样会导致如果一个定时任务发生阻塞,将会影响其他定时任务的执行,因此我们需要配置多线程执行来解决此问题。

下面这个例子说明但线程执行定时任务会出现说明什么坑

@Component
public class ScheduledTest {private static final Logger logger = LoggerFactory.getLogger(ScheduledTest.class);@Scheduled(fixedRate = 10000)public void fixedRate() throws InterruptedException {//让线程睡15秒Thread.sleep(15000L);logger.info("fixedRate开始执行");}@Scheduled(fixedDelay = 10000)public void fixedDelay() {logger.info("fixedDelay开始执行");}}

输出结果:

2020-06-17 15:02:04.424 INFO 4768 --- [ scheduling-1] c.e.d.c.scheduled.ScheduledTest : fixedRate开始执行
2020-06-17 15:02:04.425 INFO 4768 --- [ scheduling-1] c.e.d.c.scheduled.ScheduledTest : fixedDelay开始执行
2020-06-17 15:02:19.425 INFO 4768 --- [ scheduling-1] c.e.d.c.scheduled.ScheduledTest : fixedRate开始执行
2020-06-17 15:02:34.425 INFO 4768 --- [ scheduling-1] c.e.d.c.scheduled.ScheduledTest : fixedRate开始执行
2020-06-17 15:02:34.425 INFO 4768 --- [ scheduling-1] c.e.d.c.scheduled.ScheduledTest : fixedDelay开始执行

我们看结果,除了阻塞了任务,让任务不是指定时间执行的这个问题。还有一个大问题就是,大家看第二次执行定时任务,是少了fixedDelay开始执行这个定时任务输出的,说明fixedDelay这个定时任务没有执行成功。这是因为在第二次执行时,fixedRate睡眠了15秒,已经过了fixedDelay定时的10秒,所以第二个fixedDelay定时任务相当于没有执行。

那么我们就需要将定时任务变为并行执行,方法很简单,配置多个线程执行就可以了,添加配置类,实现SchedulingConfigurer 并覆写configureTasks方法,在该方法中我们将scheduledTask的单线程调度器换成我们自己创建的多线程调度器。具体线程数,要看自己的业务,我这里有两个定时任务,所以配置了两个线程。

@Configuration
public class ScheduledConfig implements SchedulingConfigurer {@Overridepublic void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {scheduledTaskRegistrar.setScheduler(Executors.newScheduledThreadPool(2));}}

再次执行上面的代码,输出以下结果

2020-06-17 15:17:40.230 INFO 15444 --- [pool-1-thread-2] c.e.d.c.scheduled.ScheduledTest : fixedDelay开始执行
2020-06-17 15:17:50.233 INFO 15444 --- [pool-1-thread-2] c.e.d.c.scheduled.ScheduledTest : fixedDelay开始执行
2020-06-17 15:17:55.230 INFO 15444 --- [pool-1-thread-1] c.e.d.c.scheduled.ScheduledTest : fixedRate开始执行
2020-06-17 15:18:00.234 INFO 15444 --- [pool-1-thread-2] c.e.d.c.scheduled.ScheduledTest : fixedDelay开始执行
2020-06-17 15:18:10.230 INFO 15444 --- [pool-1-thread-1] c.e.d.c.scheduled.ScheduledTest : fixedRate开始执行
2020-06-17 15:18:10.234 INFO 15444 --- [pool-1-thread-2] c.e.d.c.scheduled.ScheduledTest : fixedDelay开始执行
2020-06-17 15:18:20.235 INFO 15444 --- [pool-1-thread-2] c.e.d.c.scheduled.ScheduledTest : fixedDelay开始执行

从结果可以看出,两个定时任务已经分开执行,任务一不能阻塞任务二了。

有童鞋会担心,如果定时任务出错了,跑异常了,定时任务还会执行吗,下面我们就来试试

@Scheduled(fixedDelay = 10000)public void fixedDelay() throws InterruptedException {logger.info("fixedDelay开始执行");throw new InterruptedException();}

经过测试得知,当一个任务执行异常,是不影响下一次的执行,下一次还是会继续执行。


推荐阅读
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • JavaSE笔试题-接口、抽象类、多态等问题解答
    本文解答了JavaSE笔试题中关于接口、抽象类、多态等问题。包括Math类的取整数方法、接口是否可继承、抽象类是否可实现接口、抽象类是否可继承具体类、抽象类中是否可以有静态main方法等问题。同时介绍了面向对象的特征,以及Java中实现多态的机制。 ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • 预备知识可参考我整理的博客Windows编程之线程:https:www.cnblogs.comZhuSenlinp16662075.htmlWindows编程之线程同步:https ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 本文介绍了Java高并发程序设计中线程安全的概念与synchronized关键字的使用。通过一个计数器的例子,演示了多线程同时对变量进行累加操作时可能出现的问题。最终值会小于预期的原因是因为两个线程同时对变量进行写入时,其中一个线程的结果会覆盖另一个线程的结果。为了解决这个问题,可以使用synchronized关键字来保证线程安全。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • 深入理解Kafka服务端请求队列中请求的处理
    本文深入分析了Kafka服务端请求队列中请求的处理过程,详细介绍了请求的封装和放入请求队列的过程,以及处理请求的线程池的创建和容量设置。通过场景分析、图示说明和源码分析,帮助读者更好地理解Kafka服务端的工作原理。 ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • 本文介绍了Swing组件的用法,重点讲解了图标接口的定义和创建方法。图标接口用来将图标与各种组件相关联,可以是简单的绘画或使用磁盘上的GIF格式图像。文章详细介绍了图标接口的属性和绘制方法,并给出了一个菱形图标的实现示例。该示例可以配置图标的尺寸、颜色和填充状态。 ... [详细]
  • 欢乐的票圈重构之旅——RecyclerView的头尾布局增加
    项目重构的Git地址:https:github.comrazerdpFriendCircletreemain-dev项目同步更新的文集:http:www.jianshu.comno ... [详细]
author-avatar
手机用户2502871605
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有