SpringBoot 实现静态定时任务

188 阅读6分钟

前言

Spring 框架提供了 @Scheduled 注解,作用于方法上,用于标注一个定时任务,可以很方便的实现定时任务的执行。但是基于 @Scheduled 注解实现的定时任务默认为单线程,开启多个任务时,任务的执行时机会受上一个任务执行时间的影响。

针对 @Scheduled 注解实现的定时任务为单线程的问题,Spring 为开发者提供了两种解决方案:

  1. 使用 @Async 注解,此注解标注的定时任务会以多线程的方式执行
  2. 配置 ThreadPoolTaskScheduler

准备工作

pom 文件所需的依赖如下:

 <dependencies>
     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
     </dependency>
     <dependency>
         <groupId>org.projectlombok</groupId>
         <artifactId>lombok</artifactId>
     </dependency>
 </dependencies>

默认情况下,SpringBoot 的定时任务的调度是关闭的,我们需要在启动类上标注 @EnableScheduling 注解来开启定时任务的支持。

构建项目

(1)创建定时任务

 package com.project.task.job;
 ​
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Component;
 ​
 import java.time.LocalDateTime;
 ​
 /**
  * @author 孟亚辉
  * @time 2022/10/10 11:24
  */
 @Component
 @Slf4j
 public class ScheduleTask {
 ​
     /**
      * 按照标准时间来算,每隔 10s 执行一次
      */
     @Scheduled(cron = "0/10 * * * * ?")
     public void cleanJob() {
         log.info("【清理数据】:{}", LocalDateTime.now());
     }
 ​
     /**
      * 从启动时间开始,间隔 2s 执行
      * 固定间隔时间
      */
     @Scheduled(fixedRate = 2000)
     public void countJob() {
         log.info("【统计报表】:{}", LocalDateTime.now());
     }
 ​
     /**
      * 从启动时间开始,延迟 5s 后间隔 4s 执行
      * 固定等待时间
      */
     @Scheduled(fixedDelay = 4000, initialDelay = 5000)
     public void updateJob() {
         log.info("【更新数据】:{}", LocalDateTime.now());
     }
 }

(2)配置 ThreadPoolTaskScheduler

ThreadPoolTaskScheduler 意为线程池任务调度器,用于调度定时任务,我们可以在 application.yaml 配置文件中进行配置,此外,还可以使用配置类进行配置,二者选其一即可。

如果选择使用,@Async 注解来配置多线程定时任务,就必须在 SpringBoot 配置类上使用 @EnableScheduling 注解来标注,意为开启多线程的支持。

application.yaml

 spring:
   task:
     scheduling:
       # 线程池的线程名的前缀。默认为 scheduling- ,建议根据自己应用来设置
       thread-name-prefix: Task-Job- 
       pool:
         size: 20 # 线程池大小。默认为 1 ,根据自己应用来设置
       shutdown:
         # 应用关闭时,是否等待定时任务执行完成。默认为 false ,建议设置为 true
         await-termination: true 
         # 等待任务完成的最大时长,单位为秒。默认为 0 ,根据自己应用来设置
         await-termination-period: 60 

SchedulingConfigurer

 package com.project.task.config;
 ​
 import org.springframework.context.annotation.Configuration;
 import org.springframework.scheduling.annotation.SchedulingConfigurer;
 import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
 import org.springframework.scheduling.config.ScheduledTaskRegistrar;
 ​
 /**
  * @author 孟亚辉
  * @time 2022/10/10 10:50
  */
 ​
 @Configuration
 public class ScheduleThreadPoolConfig implements SchedulingConfigurer {
 ​
     //实现ScheduledTaskRegistrar中的configureTasks方法,设置调度器
     public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
         // 创建一个线程池调度器
         ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
         // 设置线程池容量
         scheduler.setPoolSize(20);
         //线程名前缀
         scheduler.setThreadNamePrefix("Task-Job-");
         // 等待时长
         scheduler.setAwaitTerminationSeconds(60);
         // 当调度器shutdown被调用时等待当前被调度的任务完成
         scheduler.setWaitForTasksToCompleteOnShutdown(true);
         // 设置当任务被取消的同时从当前调度器移除的策略
         scheduler.setRemoveOnCancelPolicy(true);
         scheduler.initialize();
         //设置任务注册器的调度器
         taskRegistrar.setTaskScheduler(scheduler);
     }
 }

如果使用了 @Async 注解来标注定时任务,则无需配置 ThreadPoolTaskScheduler。@Async 可以标注在定时任务所在的方法上,也可以标注在定时任务所在的 bean 上,表示该 bean 中的所有方法都是异步的。

测试

这里通过配置 ThreadPoolTaskScheduler 来实现

image-20221011202411668.png

知识点记录

@Scheduled 注解的参数

cron

该参数为cron表达式,从左到右:[秒] [分] [小时] [日] [月] [周] ,中间使用空格分开,例如:

@Scheduled(cron = "*/5 * * * * ?"):每五秒执行一次

我们可以用 : 在线Cron表达式生成器 来帮助我们理解 cron 表达式和书写 cron 表达式。

通用特殊字符:-*/,

  • *:表示任意值,例如 :* * * * * ?,表示每年每月每天每时每分每秒

  • ,:用于定义列表,例如:1,2,3 * * * * ? 表示 每年每月每天每时每分的每个第1秒,第2秒,第3秒

  • -:用于定义范围,例如:1-3 * * * * ? 表示每年每月每天每时每分的第1秒至第3秒

  • /:表示间隔,例如:5/10 * * * * ?表示每年每月每天每时每分,从第 5 秒开始,每 10 秒一次,即 /的左侧是开始值,右侧是间隔。如果是从 “ 0 ” 开始的话,也可以简写成 /10

日期字符:?LW#

  • ?:只可用在日期和星期部分。表示没有具体的值,使用 ?要注意冲突。日期和星期两个部分如果其中一个部分设置了值,则另一个必须设置为 ?

  • W:只能用在日期中,表示当月中最接近某天的工作日,例如 0 0 0 31W * ?,表示最接近31号的工作日,如果31号是星期六,则表示30号,即星期五,如果31号是星期天,则表示29号,即星期五。如果31号是星期三,则表示31号本身,即星期三。

  • L:表示最后(Last),只能用在日期和星期中,在日期中表示每月最后一天,在一月份中表示31号,在六月份中表示30号,也可以表示每月倒是第N天。例如: L-2表示每个月的倒数第 2 天。此外 LW 可以连起来用,例如 0 0 0 LW * ? 表示每月最后一个工作日,即每月最后一个星期五。L 用在星期中,表示星期 6,如果前面有数字,表示最后一个星期几,例如,0 0 0 ? * 6L 表示每月的最后一个星期五。

  • #:只能用在星期中,表示第几个星期几,例如:0 0 0 ? * 6#3 并表示每月的第三个星期五,即 # 前面的表示星期几,后边表示第几个。

fixedDelay

fixedDelay 从上次调用结束到下一次调用之间的固定时间(以毫秒为单位),例如:@Scheduled(fixedDelay = 5000),表示上次调用结束后 5 秒再执行

fixedDelayString

fixedDelay 意思相同,只是使用字符串的形式。唯一不同的是支持占位符

 @Scheduled(fixedDelayString = "5000")  
 //占位符的使用(配置文件中有配置:time.fixedDelay=5000):
 @Scheduled(fixedDelayString = "${time.fixedDelay}")

fixedRate

fixedRate 表示两次调用之间固定的毫秒数。例如:@Scheduled(fixedRate = 5000) 表示上次开始无论是否结束5秒钟之后会再次执行。

fixedRateString

fixedRate 意思相同,只是使用字符串的形式。唯一不同的是支持占位符。

 //上次开始无论是否结束5秒钟之后会再次执行
 @Scheduled(fixedRateString = "5000")

initialDelay

第一次执行 fixedRate 或者 fixedDelay 任务之前要延迟的毫秒数

 //第一次延迟1秒后执行,之后按fixedRate的规则每5秒执行一次
 @Scheduled(initialDelay=1000, fixedRate=5000) 

initialDelayString

initialDelay 意思相同,只是使用字符串的形式。唯一不同的是支持占位符。