Spring Task 定时任务 @Scheduled() 注解形式实现方案

53 篇文章 2 订阅
10 篇文章 0 订阅
SpringTask是Spring3.0以后自带的定时任务工具,作为轻量级的Quartz,它使用简单且无需额外依赖。文章介绍了SpringTask的单线程执行、异常处理以及非分布式环境适应性。通过@Scheduled注解可以方便地定义定时任务,如cron表达式进行任务调度。同时,文章提到了与Quartz等其他定时任务框架的对比,以及如何处理多线程和分布式环境下的任务调度问题。
摘要由CSDN通过智能技术生成

Spring Task 的介绍

很多情况下任务并非需要立即执行,而是需要在指定时间或指定频率执行,这不可能人工去操作,所以定时任务就出现了。

定时任务四种实现方案

  • Timer:这是java自带的java.util.Timer类,这个类允许你调度一个java.util.TimerTask任务。使用这种方式可以让你的程序按照某一个频度执行,但不能在指定时间运行。一般用的较少。

  • ScheduledExecutorService:也jdk自带的一个类;是基于线程池设计的定时任务类,每个调度任务都会分配到线程池中的一个线程去执行,也就是说,任务是并发执行,互不影响。

  • Spring Task:Spring3.0以后自带的task,可以将它看成一个轻量级的Quartz,而且使用起来比Quartz简单许多。

  • Quartz:这是一个功能比较强大的的调度器,可以让你的程序在指定时间执行,也可以按照某一个频度执行,配置起来稍显复杂。

为什么不选用quartz?

虽然quartz性能更强,但spring Task比较简单,写了一个demo可以满足需求,就选用这个。当时项目赶进度,就使用这个了。 先有后优

本文主要介绍Spring Task的使用和实现原理。

Spring Task特点

SprngTask没有专门的包,是Spring 3.0自带的定时任务,其核心类位于spring-context包中,所以引入spring的核心包此功能即可使用。可以将它看作成一个轻量级的Quartz,功能虽然没有Quartz那样强大,但是使用起来非常简单,无需增加额外的依赖,可直接上手使用。它具备如下特点:

  • 默认单线程同步执行

SpringTask 默认是单线程的,不同定时任务使用的都是同一个线程;当存在多个定时任务时,若当前任务执行时间过长则可能导致下一个任务无法执行。

在实际开发中,不希望所有的任务都运行在一个线程中。可在配置类中使用

ScheduledTaskRegistrar#setTaskScheduler(TaskScheduler taskScheduler),SpringTask提供一个基于多线程的TaskScheduler接口的实现类(譬如ThreadPoolTaskScheduler类),Spring默认使用ConcurrentTaskScheduler。

  • 对异常的处理

在SpringTask中,一旦某个任务在执行过程中抛出异常,则整个定时器生命周期就结束,以后永远不会再执行定时器任务。需要手动处理异常

  • 默认不适用分布式环境

Spring Task 并不是为分布式环境设计的,在分布式环境下,这种定时任务是不支持集群配置的,如果部署到多个节点上,各个节点之间并没有任何协调通讯机制,集群的节点之间是不会共享任务信息的,每个节点上的任务都会按时执行,导致任务的重复执行。

我们可以使用支持分布式的定时任务调度框架,比如 Quartz、XXL-Job、Elastic Job。

当然你可以借助 zookeeper 、 redis 等实现分布式锁来处理各个节点的协调问题。

或者把所有的定时任务抽成单独的服务单独部署。 本项目即是需要使用分布式锁

Springboot项目基于@Scheduled注解引入定时任务

操作流程

2. 使用@EnableScheduling注解开启定时任务,该注解放在启动类上。

@SpringBootApplication
@EnableScheduling //开启定时任务
public class ScheduledDemoApplication
{
    public static void main(String[] args)
    {
        SpringApplication.run(ScheduledDemoApplication.class, args);
    }
}

2.  使用@Scheduled注解定义定时任务

public class SaticScheduleTask {
    //定义定时任务
    @Scheduled(cron = "0/5 * * * * ?")
    private void configureTasks() {
        System.err.println("执行静态定时任务时间: " + LocalDateTime.now());
    }
}

其中,使用这个注解的方法必需无参,无返回值,若有返回值将被忽略。

参数说明

@Scheduled 注解的参数有以下四种,分别对应四种任务调度策略

  • cron表达式

“秒 分 时 天 月 周”,其他语法参照资料

cron表达式配置了在哪一刻执行任务,会在配置的任务开始时间判断任务是否可以执行,如果能则执行,不能则会跳过本次执行;每次任务不一定都会执行。
强调在某时某分某刻执行定时任务

  • fixedDelay (固定的延迟,结束-开始的间隔)

设定上一个任务结束后多久执行下一个任务,即上一任务的结束时间和下一任务的开始时间

每次任务都会执行

  • fixedRate (固定的频率,开始-开始的间隔)

设定上一个任务开始后多久执行下一个任务,即上一个任务的开始时间到下一个任务开始时间的间隔

特别地,若到达任务的开始执行时间,但上一个任务却没有完成时,spring会等待上一个任务执行完,并立即开始执行本次任务。

每次任务都会执行

  • initialDelay(初始化延迟)

需要配合fixedDelay或fixedRate使用
设定延迟多长时间后开始执行第一次定时任务,其后按照fixedDelay或fixedRate的逻辑执行。

需要注意的是,由于默认使用单线程执行定时任务,因此所有的定时任务是串行执行的。
 

@Scheduled注解参数的详细说明

1. 基本用法

@Scheduled 由Spring定义,用于将方法设置为定时任务调度。如:方法每隔十秒钟被执行、方法在固定时间点被执行等,启动类需要加@EnableScheduling表示开启任务定时任务调度。

  • @Scheduled(fixedDelay = 1000)

上一个任务结束到下一个任务开始的时间间隔为固定的1秒,任务的执行总是要先等到上一个任务的执行结束

  • @Scheduled(fixedRate = 1000)

每间隔1秒钟就会执行任务(如果任务执行的时间超过1秒,则下一个任务在上一个任务结束之后立即执行)

  • @Scheduled(fixedDelay = 1000, initialDelay = 2000)

第一次执行的任务将会延迟2秒钟后才会启动

  • @Scheduled(cron = “0 15 10 15 * ?”)

cron表达式,每个月的15号上午10点15分开始执行任务

  • 配置文件中配置任务调度的参数

@Scheduled(fixedDelayString = "fixedDelay.in.milliseconds")
 
@Scheduled(fixedRateString="fixedDelay.in.milliseconds")
 
@Scheduled(fixedRateString="{fixedRate.in.milliseconds}")
 
@Scheduled(cron = "${cron.expression}")

2. Cron表达式

Cron表达式是一个字符串,字符串以5或6个空格隔开,分为6或7个域,每一个域代表一个含义,cron表达式语法格式:

{秒数} {分钟} {小时} {日期} {月份} {星期} {年份(可为空)}

1.cron表达式各占位符解释

从左到右用空格隔开分别是:秒 分 时 日 月 周 年(可省略)

  • 第一个位置:Seconds 秒:区间 0-59 秒,代表一分钟内的秒数。
  • 第二个位置:Minutes 分:区间 0-59 分,代表一小时内的分钟数。
  • 第三个位置:Hours 时:区间 0-23 时,代表一天中的小时数。
  • 第四个位置:Day of month 日:区间 1-31 (?根据每月有多少天来),代表一月中的多少号。
  • 第五个位置:Month 月:区间 1-12 ,代表一年中的月份。
  • 第六个位置:Day of week 周:区间 1-7或者英文星期的缩写,代表星期几。(注意:1 对应周日 SUN , 7 对应周六 SAT ,如此循环)
  • 第七个位置:Year 年:区间1970-2099,代表具体年份,但是一般不用,可以直接不写。

表格如下:

是否必填

值以及范围

通配符

0-59

, - * /

0-59

, - * /

0-23

, - * /

1-31

, - * ? / L W

1-12 或 JAN-DEC

, - * /

1-7 或 SUN-SAT

, - * ? / L #

1970-2099

, - * /

2. cron表达式各通配符解释

描述:写在cron表达式的标识符位置用于表达特定意义的字符,如 例1 中的:0,2,*,?

阿拉伯数字:数值,出现在标识符位置的数字代表对应值,比如第一个2代表两点,第二个2代表二号

* :通配,语义是每..., 比如第五个位置的 *就表示每月都会执行(相当于1-12)

? :忽略,仅被用于天(月)和天(星期)两个子表达式,表示不指定值。语义相当于无论...,表示不确定的值, 比如第六个位置的?就表示不管当前是周几就会执行。至于为什么会有这种用法,它也匹配域的任意值,但实际不会。因为DayofMonth和DayofWeek会相互影响。例如想在每月的20日触发调度,不管20日到底是星期几,则只能使用如下写法: 13 13 15 20 * ?, 其中最后一位只能用?,而不能使用*,如果使用*表示不管星期几都会触发,实际上并不是这样。

/ :代表步进(step),间隔,/前面的值代表初始值(““等同”0”),后面的值代表偏移量。语义是每隔...,比如例2中的第三个位置的2/5就表示从2点开始每隔五小时。

- :区间,语义相当于第…到…的每… 比如例2中的第二个位置的15-20就表示第15分钟到20分钟之间的每分钟

, :枚举,语义相当于第…和… 比如例2中的第一个位置的15,20,40就表示第15秒、20秒和40秒

L :最后(last),语义相当于最后一个 比如例2中的第四个位置的L就表示最后一天

W :工作日,字面意思,就是工作日 比如例3中的第四个位置的15W表示15号附近最近的工作日,如果15号刚好是工作日那就15号触发,如果15号是周六那就14号触发,如果15号是周日那就16号触发。前面不带数字就是所有匹配的工作日。

# :周定位,语义相当于每月的第几个周几 比如例4中的第六个位置的2#3就表示第三个周一。

3.常用Cron表达式( 秒/分/时/日/月/周/年 )

  1. 每隔5秒执行一次:*/5 * * * * ?
  2. 每隔1分钟执行一次:0 */1 * * * ?
  3. 每天23点执行一次:0 0 23 * * ?
  4. 每天凌晨1点执行一次:0 0 1 * * ?
  5. 每月1号凌晨1点执行一次:0 0 1 1 * ?
  6. 每月最后一天23点执行一次:0 0 23 L * ?
  7. 每周星期天凌晨1点实行一次:0 0 1 ? * L
  8. 每天上午10点,下午2点,4点:0 0 10,14,16 * * ?
  9. 朝九晚五工作时间内每半小时:0 0/30 9-17 * * ?
  10. 表示每个星期三中午12点:0 0 12 ? * WED
  11. 每天中午12点触发:"0 0 12 * * ?"
  12. 在每天下午2点到下午2:59期间的每1分钟触发:"0 * 14 * * ?"
  13. 在每天下午2点到下午2:55期间的每5分钟触发:"0 0/5 14 * * ?"
  14. 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发:"0 0/5 14,18 * * ?"
  15. 在每天下午2点到下午2:05期间的每1分钟触发:"0 0-5 14 * * ?"
  16. 每年三月的星期三的下午2:10和2:44触发:"0 10,44 14 ? 3 WED"
  17. MON-FRI" 周一至周五的上午10:15触发:"0 15 10 ? *
  18. 每月15日上午10:15触发:"0 15 10 15 * ?"
  19. 每月最后一日的上午10:15触发:"0 15 10 L * ?"
  20. 每月的最后一个星期五上午10:15触发:"0 15 10 ? * 6L"
  21. 2002年至2005年的每月的最后一个星期五上午10:15触发:"0 15 10 ? * 6L 2002-2005"
  22. 每月的第三个星期五上午10:15触发:"0 15 10 ? * 6#3"
  23. 每天上午10:15触发:"0 15 10 ? * *"
  24. 每天上午10:15触发:"0 15 10 * * ?"
  25. 每天上午10:15触发:"0 15 10 * * ? *"
  26. 2005年的每天上午10:15触发:"0 15 10 * * ? 2005"

注:corn表达式 0/10 和 */10 的区别

0/5 和 */5 在cron表达式中表示的意义是相同的,都表示每隔5个单位执行一次。有解释说,0/5 表示从0开始,每隔5个单位执行一次,而 */5 表示每隔5个单位执行一次。所以它们的区别在于写法上的不同,但实际上表示的含义是相同的。其实则不然

在说这两者的差别之前,先说下各个字符代表的含义。0代表从0分开始,*代表任意字符,代表递增。
也就是说0 0/10 * * *代表从0分钟开始,每10分钟执行任务一次。0 */10 * * *代表从任务启动开始每10分钟执行任务一次。有人会问,这不是一样的么?

是不一样的。因为起始的时间不一样。例如:从6:07分钟的时候执行该任务第一种写法会在6:10的时候进行执行,写法二会在6:17进行执行。这就是两者的差别。

当然0 0/1 * * * 与0 */1 * * *有时会被认为是同一种写法。  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值