1. 程式人生 > >【Spring】Spring的定時任務註解@Scheduled原來如此簡單

【Spring】Spring的定時任務註解@Scheduled原來如此簡單

1 簡介

定時任務的實現非常多,JDK的Timer、Spring提供的輕量級的Scheduled Task、QuartZ和Linux Cron等,還有一些分散式的任務排程框架。本文主要介紹Scheduled Task的使用。

2 方便的4種方式

註解@Scheduled只能用於滿足下面兩個條件的方法上:

(1)沒有返回型別,或者說返回型別為void

(2)沒有引數;

開啟Spring的Scheduler非常簡單,一個註解@EnableScheduling即可:

@Configuration
@EnableScheduling
public class SchedulingConfig {
}

如果是Springboot應用,則直接在啟動類上面加上@EnableScheduling就可以使用了。

2.1 固定延遲fixedDelay

代表下一個任務的開始與上一個任務的結束間隔總是固定的時長,而且總是會等上一個任務完成了,才會開啟下一個任務。如果需求是有這樣依賴要求的,使用這種模式是非常合適的。程式碼如下:

@Scheduled(fixedDelay = 1000)
public void fixedDelay() {
  log.info("fixedDelay");
}

引數為1000,代表固定延遲為1000毫秒,即1秒鐘,所以輸出為:

2019-11-19 21:02:43,977 scheduling-1:fixedDelay 
2019-11-19 21:02:44,981 scheduling-1:fixedDelay 
2019-11-19 21:02:45,983 scheduling-1:fixedDelay 
2019-11-19 21:02:46,984 scheduling-1:fixedDelay 

2.2 固定頻率fixedRate

2.2.1 正常情況

定頻任務的特性是任務的執行的時間間隔總是一樣的。比如每1小時執行一次,就是任務執行開始的時間點的時間間隔為1小時。程式碼如下:

@Scheduled(fixedRate = 2000)
public void fixedRate() {
  log.info("fixedRate");
}

引數為2000,則每2秒執行一次,輸出為:

2019-11-19 21:38:45,073 scheduling-1:fixedRate 
2019-11-19 21:38:47,076 scheduling-1:fixedRate 
2019-11-19 21:38:49,073 scheduling-1:fixedRate 
2019-11-19 21:38:51,075 scheduling-1:fixedRate 

2.2.2 預設單執行緒

需要注意的是它預設是單執行緒的,不會並行執行。即使是固定頻率,但下一次的任務也必須等到上一次任務執行完畢才會開始。下面這個例子能很好說明:

@Scheduled(fixedRate = 1000)
public void fixedRateLongTimeTask() throws InterruptedException {
  log.info("fixedRateLongTimeTask");
  Thread.sleep(3000);
}

由於任務需要執行3秒才能完成,即使fixedRate設定為1秒,並不能每一秒執行一次,輸出如下:

2019-11-19 21:46:00,108 scheduling-1:fixedRateLongTimeTask 
2019-11-19 21:46:03,113 scheduling-1:fixedRateLongTimeTask 
2019-11-19 21:46:06,113 scheduling-1:fixedRateLongTimeTask 
2019-11-19 21:46:09,117 scheduling-1:fixedRateLongTimeTask 

每3次輸出一次。

2.2.3 註解@Async來幫你

上述問題有辦法解決嗎?答案是肯定的,而且非常簡單。只需要加一個註解@Async就可以使任務能非同步多執行緒地執行了,程式碼如下:

@Async
@Scheduled(fixedRate = 1000)
public void fixedRateLongTimeTask() throws InterruptedException {
  log.info("fixedRateLongTimeTask");
  Thread.sleep(3000);
}

通過日誌可以看出是每秒執行一次的,即使前面的任務還沒有完成。而且執行緒名不一樣,通過多執行緒來執行,輸出結果為:

2019-11-19 21:54:22,261 task-5:fixedRateLongTimeTask 
2019-11-19 21:54:23,257 task-6:fixedRateLongTimeTask 
2019-11-19 21:54:24,257 task-4:fixedRateLongTimeTask 
2019-11-19 21:54:25,257 task-8:fixedRateLongTimeTask 
2019-11-19 21:54:26,259 task-1:fixedRateLongTimeTask 
2019-11-19 21:54:27,262 task-2:fixedRateLongTimeTask 
2019-11-19 21:54:28,260 task-3:fixedRateLongTimeTask 

注意:需要指出的是,需要像@EnableScheduling一樣,需要新增配置註解@EnableAsync來開啟這個功能開關。另外,如果任務執行時間很長,例如1分鐘,情況又不一樣。以後再詳細介紹@Async的使用吧。

2.3 初始延遲initialDelay

初始延遲是用initialDelay來指定的,它可以延遲第一次任務執行的時間。如下例子的引數為30秒,則在啟動30秒後,才開始執行第一次。可以減輕專案啟動的負擔,也可以為任務執行前準備資料。

@Scheduled(fixedDelay = 1000, initialDelay = 30*1000)
public void fixedDelayWithIntialDelay() {
  log.info("fixedDelayWithIntialDelay");
}

輸出如下:

2019-11-19 22:10:02,092 main:Tomcat started on port(s): 443 (http) with context path '' 
2019-11-19 22:10:02,095 main:Started DemoApplication in 1.272 seconds (JVM running for 1.767) 
2019-11-19 22:10:32,063 scheduling-1:fixedDelayWithIntialDelay 
2019-11-19 22:10:33,067 scheduling-1:fixedDelayWithIntialDelay 
2019-11-19 22:10:34,069 scheduling-1:fixedDelayWithIntialDelay 
2019-11-19 22:10:35,069 scheduling-1:fixedDelayWithIntialDelay

可以看出,在專案啟動後30秒左右,才開始執行任務。

2.4 Cron表示式

上述提供的功能並不能滿足定時任務排程的所有需求,比如需要每個月1號傳送簡訊,每週六做資料分析等。這裡Cron表示式就派上用場了。

下面的例子表示每當秒數為06的時候就執行。程式碼如下:

@Scheduled(cron = "6 * * ? * *")
public void cron() {
  log.info("cron");
}

結果如下:

2019-11-19 22:20:06,003 scheduling-1:cron 
2019-11-19 22:21:06,004 scheduling-1:cron 
2019-11-19 22:22:06,002 scheduling-1:cron 

Cron表示式功能非常強大,網上資料很豐富,這裡不展開講了。

3 引數配置化

之前的例子都將引數寫死在程式碼上了,如果需要更靈活,其實可以用引數來配置。這樣需要修改引數的時候,不用修改程式碼、編譯打包再部署了,直接修改配置檔案即可。

程式碼如下:

@Scheduled(cron = "${pkslow.cron}")
public void cronWithConfig() {
  log.info("cronWithConfig");
}

在application.properties配置如下:

pkslow.cron=* * * ? * *

程式碼1秒執行一次。

4 如果她突然消失了

由於Spring的Scheduler預設是單執行緒的,這樣會存在一個問題,如果某個任務執行卡住了,那就無法繼續往下執行了。在日誌上表現就是突然消失了。這種情況出現的概率還是不小的,如操作資料庫死鎖了,http請求timeout為無限等待,還有其它原因的死鎖等。

當遇到這種情況,應通過命令jstack pid > pid.ThreadDump.txt獲取當前執行緒情況,然後分析是否真的是卡住了,卡在了哪個環節,然後再分析具體程式碼。通過設定超時或重試等方法來解決。

5 結論

本文主要介紹了Spring的定時任務註解@Scheduled的使用,講述了多種方式的使用和配置。它非常方便簡潔,對於簡單的定時任務足以應對了。


歡迎關注公眾號<南瓜慢說>,將持續為你更新...

多讀書,多分享;多寫作,多整理