前言
Spring 框架提供了 @Scheduled 注解,作用于方法上,用于标注一个定时任务,可以很方便的实现定时任务的执行。但是基于 @Scheduled 注解实现的定时任务默认为单线程,开启多个任务时,任务的执行时机会受上一个任务执行时间的影响。
针对 @Scheduled 注解实现的定时任务为单线程的问题,Spring 为开发者提供了两种解决方案:
- 使用
@Async注解,此注解标注的定时任务会以多线程的方式执行 - 配置 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 来实现
知识点记录
@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
日期字符:
?、L、W、#
-
?:只可用在日期和星期部分。表示没有具体的值,使用?要注意冲突。日期和星期两个部分如果其中一个部分设置了值,则另一个必须设置为?。 -
W:只能用在日期中,表示当月中最接近某天的工作日,例如0 0 0 31W * ?,表示最接近31号的工作日,如果31号是星期六,则表示30号,即星期五,如果31号是星期天,则表示29号,即星期五。如果31号是星期三,则表示31号本身,即星期三。 -
L:表示最后(Last),只能用在日期和星期中,在日期中表示每月最后一天,在一月份中表示31号,在六月份中表示30号,也可以表示每月倒是第N天。例如: L-2表示每个月的倒数第 2 天。此外L和W可以连起来用,例如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 意思相同,只是使用字符串的形式。唯一不同的是支持占位符。