定時任務的實現很是多,JDK的Timer、Spring提供的輕量級的Scheduled Task、QuartZ和Linux Cron等,還有一些分佈式的任務調度框架。本文主要介紹Scheduled Task的使用。java
註解@Scheduled
只能用於知足下面兩個條件的方法上:數據庫
(1)沒有返回類型,或者說返回類型爲void
;多線程
(2)沒有參數;app
開啓Spring的Scheduler很是簡單,一個註解@EnableScheduling
便可:框架
@Configuration @EnableScheduling public class SchedulingConfig { }
若是是Springboot應用,則直接在啓動類上面加上@EnableScheduling
就可使用了。異步
表明下一個任務的開始與上一個任務的結束間隔老是固定的時長,並且老是會等上一個任務完成了,纔會開啓下一個任務。若是需求是有這樣依賴要求的,使用這種模式是很是合適的。代碼以下:async
@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
定頻任務的特性是任務的執行的時間間隔老是同樣的。好比每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
須要注意的是它默認是單線程的,不會並行執行。即便是固定頻率,但下一次的任務也必須等到上一次任務執行完畢纔會開始。下面這個例子能很好說明:
@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次輸出一次。
上述問題有辦法解決嗎?答案是確定的,並且很是簡單。只須要加一個註解@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
的使用吧。
初始延遲是用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秒左右,纔開始執行任務。
上述提供的功能並不能知足定時任務調度的全部需求,好比須要每月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表達式功能很是強大,網上資料很豐富,這裏不展開講了。
以前的例子都將參數寫死在代碼上了,若是須要更靈活,其實能夠用參數來配置。這樣須要修改參數的時候,不用修改代碼、編譯打包再部署了,直接修改配置文件便可。
代碼以下:
@Scheduled(cron = "${pkslow.cron}") public void cronWithConfig() { log.info("cronWithConfig"); }
在application.properties配置以下:
pkslow.cron=* * * ? * *
代碼1秒執行一次。
因爲Spring的Scheduler默認是單線程的,這樣會存在一個問題,若是某個任務執行卡住了,那就沒法繼續往下執行了。在日誌上表現就是忽然消失了。這種狀況出現的機率仍是不小的,如操做數據庫死鎖了,http請求timeout爲無限等待,還有其它緣由的死鎖等。
當遇到這種狀況,應經過命令jstack pid > pid.ThreadDump.txt
獲取當前線程狀況,而後分析是否真的是卡住了,卡在了哪一個環節,而後再分析具體代碼。經過設置超時或重試等方法來解決。
本文主要介紹了Spring的定時任務註解@Scheduled
的使用,講述了多種方式的使用和配置。它很是方便簡潔,對於簡單的定時任務足以應對了。
歡迎關注公衆號<南瓜慢說>,將持續爲你更新...
多讀書,多分享;多寫做,多整理。