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