速看!Spring Boot任務(wù)調(diào)度你不知道的使用技巧
環(huán)境:SpringBoot3.2.5
1. TaskScheduler接口
Spring 提供了一個TaskSchedulerSPI,其中包含各種用于調(diào)度任務(wù)的方法,以便在未來某個時間點(diǎn)運(yùn)行。下面的列表顯示了 TaskScheduler 接口定義:
public interface TaskScheduler {
ScheduledFuture schedule(Runnable task, Trigger trigger);
ScheduledFuture scheduleAtFixedRate(Runnable task, Instant startTime, Duration period);
ScheduledFuture scheduleWithFixedDelay(Runnable task, Instant startTime, Duration delay);
}
上面接口只列出了核心方法。
在SpringBoot環(huán)境中,我們可以直接注入該接口,如下示例:
@Resource
private TaskScheduler taskScheduler ;
@PostConstruct
public void initScheduler() {
this.taskScheduler.schedule(() -> {
System.out.println("執(zhí)行任務(wù)") ;
}, new CronTrigger("*/2 * * * * *")) ;
}
上面通過cron表達(dá)式控制任務(wù)執(zhí)行周期。你也可以設(shè)置固定的執(zhí)行速率。
this.taskScheduler.scheduleAtFixedRate(() -> {
System.out.println("固定周期指定任務(wù)") ;
}, Duration.ofSeconds(2)) ; // 每2s執(zhí)行
在默認(rèn)情況下SpringBoot創(chuàng)建的是ThreadPoolTaskScheduler
坑點(diǎn):默認(rèn)情況創(chuàng)建的ThreadPoolTaskScheduler只有一個線程,如果你當(dāng)前有多個定時任務(wù),如果出現(xiàn)重合那么任務(wù)會排隊(duì)執(zhí)行。通過如下參數(shù)修改線程池大小
spring:
task:
scheduling:
thread-name-prefix: pack-task
pool:
size: 2
這里的spring.task.scheduling.pool.size默認(rèn)值為:1
2. 基于注解任務(wù)調(diào)用
基于注解方式實(shí)現(xiàn)任務(wù)的調(diào)用應(yīng)該是我們工作中應(yīng)用的主要方式,非常簡單方便,如下示例:
// 以固定的時間間隔執(zhí)行帶有注釋的方法,該時間間隔從上一次調(diào)用結(jié)束到下一次調(diào)用開始。
@Scheduled(fixedDelay = 2000)
public void fixedDelayTask() throws Exception {
System.err.printf("Current Time: %s, Current Thread: %s%n", new SimpleDateFormat("HH:mm:ss").format(new Date()), Thread.currentThread().getName()) ;
TimeUnit.SECONDS.sleep(1) ;
}
輸出結(jié)果
雖然間隔設(shè)置為2s,但是實(shí)際輸出是3s。
使用corn表達(dá)式
@Scheduled(cron = "*/3 * * * * *")
public void task1() {
// TODO
}
每隔3s執(zhí)行
響應(yīng)式支持
從Spring Framework 6.1開始,幾種類型的反應(yīng)式方法也支持@Scheduled方法:
@Scheduled(fixedRate = 2, timeUnit = TimeUnit.SECONDS)
public Flux<Integer> reactiveTask() {
return Flux.just(1, 2, 3).doOnNext(System.err::println) ;
}
在上面的示例中,會每隔2s打印1,2,3。
3. Cron表達(dá)式
一個格式良好的 cron 表達(dá)式(如 * * * * * *)由六個空格分隔的時間和日期字段組成,每個字段都有自己的有效值范圍:
特別說明:
- 在范圍(或 *)后面加上 /,表示數(shù)字值在范圍內(nèi)的間隔
- 用連字符 (-) 分隔的兩個數(shù)字表示一個數(shù)字范圍。指定的范圍是包含在內(nèi)的。如(10-15 * * * * *):每分鐘內(nèi)的10,11,12,13,14,15秒時都會運(yùn)行
- 逗號 (,) 用于分隔列表中的項(xiàng)目;如(0 0 6,19 * * *)每天上午 6:00 和下午 7:00
寫cron表達(dá)式不太好理解。為了提高可讀性,Spring 支持以下表示常用序列的宏。因此,可以使用這些宏來代替六位數(shù)值:@Scheduled(cron = "@hourly")。
宏 | 描述 |
@yearly (or @annually) | 每年一次 (0 0 0 1 1 *) |
@monthly | 每月一次 (0 0 0 1 * *) |
@weekly | 每周一次 (0 0 0 * * 0) |
@daily (or @midnight) | 每天一次(0 0 0 * * *) |
@hourly | 每小時一次 (0 0 * * *) |
如下示例:
@Scheduled(cron = "@hourly")
public void task2() {
System.out.println("宏指令執(zhí)行任務(wù)") ;
}
這樣寫簡單多了。
4. 虛擬線程支持
從Spring6.1開始,支持虛擬線程(JDK21)執(zhí)行任務(wù)的調(diào)用。在SpringBoot環(huán)境下你需要開啟功能:
spring:
threads:
virtual:
enabled: true
如下示例:
@Scheduled(cron = "*/3 * * * * *")
public void scheduler1() throws Exception {
System.err.printf("當(dāng)前時間: %s, 當(dāng)前線程: %s, 是否虛擬線程: %b%n", new SimpleDateFormat("HH:mm:ss").format(new Date()), Thread.currentThread().getName(), Thread.currentThread().isVirtual()) ;
}
輸出結(jié)果
圖片
如果使用了虛擬線程,那么下面的配置將沒有任何的意義。
spring:
task:
scheduling:
pool:
size: 10 #無意義
使用虛擬線程后,任務(wù)調(diào)用將使用單個調(diào)度線程,但每次執(zhí)行計(jì)劃任務(wù)時都會啟動一個新線程(虛擬線程)。
5. 自定義任務(wù)調(diào)度配置
我們可以通過實(shí)現(xiàn)SchedulingConfigurer接口來自定義相關(guān)任務(wù)調(diào)度的設(shè)置,這通常用于設(shè)置在執(zhí)行計(jì)劃任務(wù)時使用的特定TaskScheduler bean,或者以編程方式注冊計(jì)劃任務(wù)。如下示例:
@Component
public class PackSchedulingConfigurer implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler() ;
taskScheduler.setThreadNamePrefix("my-task-") ;
taskScheduler.afterPropertiesSet();
taskRegistrar.setTaskScheduler(taskScheduler );
}
}
上面的示例修改任務(wù)調(diào)度執(zhí)行的線程池對象。
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.addCronTask(() -> {
System.out.println("動態(tài)注冊調(diào)度任務(wù)...") ;
}, "*/2 * * * * *");
}
上面示例,通過編程的方式動態(tài)注冊調(diào)度任務(wù)。