@Scheduled阻塞導致未執行生效的情況分析
今天排查線上數據,發現數據並未更新,查看日志發現更新數據的定時任務並沒有執行,而執行該定時任務的時間發現執行了另外的定時任務,所以因此初步判斷可能是定時任務阻塞導致相同時間的定時任務有未執行任務。
寫了個DEMO果真復現了,@Scheduled注解的定時任務為單線程執行,所以必定會有阻塞情況。
測試代碼
- 定時任務【1】DEMO代碼
@Component
public class Test01 {
// 每秒執行一次
@Scheduled(cron = "0/1 * * * * ? ")
public void test() {
// 時分秒
String nowTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"));
// 日志打印
System.out.println(nowTime + " 任務【1】執行 線程:" + Thread.currentThread().getName());
}
}
- 定時任務【2】DEMO代碼
@Component
public class Test02 {
// 每5秒執行一次
@Scheduled(cron = "0/5 * * * * ? ")
public void test() {
String nowTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"));
System.out.println(nowTime + " 任務【2】執行 線程:" + Thread.currentThread().getName());
try {
// 模擬耗時任務,阻塞2s
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
服務啟動后運行結果
原因分析
- 由程序運行后的打印結果可以發現兩個任務均是使用的同一線程。
- 任務【1】應該是每秒執行一次的,但是執行時間上可以發現,任務【1】的執行時間不是連續的,這就說明有時間點是任務【1】沒有執行的,而這些時間點恰好任務【2】正在執行中,單線程的原因線程此時阻塞,從而導致這些時間點任務【1】沒有執行。
解決方案
- 添加@Async注解或者使用自定義線程池執行任務
代碼這里就不貼了,就是在上面的任務【1】和任務【2】的@Scheduled注解上面添加一個注解@Async即可。
多線程執行定時任務后的執行結果
很明顯,任務【1】每秒執行的時間連續了!!!沒有未執行的情況。
但需要注意的是,可以對@Async進行自定義配置,使其使用時,內部也是通過創建ThreadPoolExecutor線程池來執行。
- 【推薦】一勞永逸,統一配置:實現SchedulingConfigurer接口
@Configuration
public class ScheduledConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
// 自定義調度器,設置為一個支持定時及周期性的任務執行的線程池,這里初始3個線程
scheduledTaskRegistrar.setScheduler(Executors.newScheduledThreadPool(3));
}
}
統一配置后便不需要添加@Async注解或者創建線程池來達到多線程了
執行結果
很明顯,定時任務執行的線程達到多線程執行,任務【1】執行時間連續,沒有出現未執行的情況,且線程也是反復使用的三個。
查看setScheduler()方法源碼:
由源碼也可以看出,自定義調度器的時候,只能設置TaskScheduler
和ScheduledExecutorService
類型,除此之外的類型都會報錯。