在實際項目開發中,除了Web應用、SOA服務外,還有一類不可缺乏的,那就是定時任務調度。定時任務的場景能夠說很是普遍,好比某些視頻網站,購買會員後,天天會給會員送成長值,每個月會給會員送一些電影券;好比在保證最終一致性的場景中,每每利用定時任務調度進行一些比對工做;好比一些定時須要生成的報表、郵件;好比一些須要定時清理數據的任務等。本篇博客將系統的介紹定時任務調度,會涵蓋Timer、ScheduledExecutorService、開源工具包Quartz,以及Spring和Quartz的結合等內容。java
定時任務調度:基於給定的時間點、給定的時間間隔、給定的執行次數自動執行的任務。編程
Timer位於java.util包下,其內部包含且僅包含一個後臺線程(TimeThread)對多個業務任務(TimeTask)進行定時定頻率的調度。併發
schedule的四種用法和scheduleAtFixedRate的兩種用法框架
參數說明:分佈式
task:所要執行的任務,須要extends TimeTask override run()ide
time/firstTime:首次執行任務的時間工具
period:週期性執行Task的時間間隔,單位是毫秒網站
delay:執行task任務前的延時時間,單位是毫秒ui
很顯然,經過上述的描述,咱們能夠實現:.net
延遲多久後執行一次任務;指定時間執行一次任務;延遲一段時間,並週期性執行任務;指定時間,並週期性執行任務;
思考1:若是time/firstTime指定的時間,在當前時間以前,會發生什麼呢?
在時間等於或者超過time/firstTime的時候,會執行task!也就是說,若是time/firstTime指定的時間在當前時間以前,就會當即獲得執行。
思考2:schedule和scheduleAtFixedRate有什麼區別?
scheduleAtFixedRate:每次執行時間爲上一次任務開始起向後推一個period間隔,也就是說下次執行時間相對於上一次任務開始的時間點,所以執行時間不會延後,可是存在任務併發執行的問題。
schedule:每次執行時間爲上一次任務結束後推一個period間隔,也就是說下次執行時間相對於上一次任務結束的時間點,所以執行時間會不斷延後。
思考3:若是執行task發生異常,是否會影響其餘task的定時調度?
若是TimeTask拋出RuntimeException,那麼Timer會中止全部任務的運行!
思考4:Timer的一些缺陷?
前面已經說起到Timer背後是一個單線程,所以Timer存在管理併發任務的缺陷:全部任務都是由同一個線程來調度,全部任務都是串行執行,意味着同一時間只能有一個任務獲得執行,而前一個任務的延遲或者異常會影響到以後的任務。
其次,Timer的一些調度方式還算比較簡單,沒法適應實際項目中任務定時調度的複雜度。
一個簡單的Demo實例
Timer其餘須要關注的方法
cancel():終止Timer計時器,丟棄全部當前已安排的任務(TimeTask也存在cancel()方法,不過終止的是TimeTask)
purge():從計時器的任務隊列中移除已取消的任務,並返回個數
因爲Timer存在的問題,JDK5以後便提供了基於線程池的定時任務調度:ScheduledExecutorService。
設計理念:每個被調度的任務都會被線程池中的一個線程去執行,所以任務能夠併發執行,並且相互之間不受影響。
咱們直接看例子:
雖然ScheduledExecutorService對Timer進行了線程池的改進,可是依然沒法知足複雜的定時任務調度場景。所以OpenSymphony提供了強大的開源任務調度框架:Quartz。Quartz是純Java實現,並且做爲Spring的默認調度框架,因爲Quartz的強大的調度功能、靈活的使用方式、還具備分佈式集羣能力,能夠說Quartz出馬,能夠搞定一切定時任務調度!
Quartz的體系結構
先來看一個Demo:
說明:
一、從代碼上來看,有XxxBuilder、XxxFactory,說明Quartz用到了Builder、Factory模式,還有很是易懂的鏈式編程風格。
二、Quartz有3個核心概念:調度器(Scheduler)、任務(Job&JobDetail)、觸發器(Trigger)。(一個任務能夠被多個觸發器觸發,一個觸發器只能觸發一個任務)
三、注意當Scheduler調度Job時,實際上會經過反射newInstance一個新的Job實例(待調度完畢後銷燬掉),同時會把JobExecutionContext傳遞給Job的execute方法,Job實例經過JobExecutionContext訪問到Quartz運行時的環境以及Job自己的明細數據。
四、JobDataMap能夠裝載任何能夠序列化的數據,存取很方便。須要注意的是JobDetail和Trigger均可以各自關聯上JobDataMap。JobDataMap除了能夠經過上述代碼獲取外,還能夠在YourJob實現類中,添加相應setter方法獲取。
五、Trigger用來告訴Quartz調度程序何時執行,經常使用的觸發器有2種:SimpleTrigger(相似於Timer)、CronTrigger(相似於Linux的Crontab)。
六、實際上,Quartz在進行調度器初始化的時候,會加載quartz.properties文件進行一些屬性的設置,好比Quartz後臺線程池的屬性(threadCount)、做業存儲設置等。它會先從工程中找,若是找不到那麼就是用quartz.jar中的默認的quartz.properties文件。
七、Quartz存在監聽器的概念,好比任務執行先後、任務的添加等,能夠方便實現任務的監控。
CronTrigger示例
這裏給出一些經常使用的示例:
0 15 10 ? * 天天10點15分觸發
0 15 10 ? 2017 2017年天天10點15分觸發
0 14 * ? 天天下午的 2點到2點59分每分觸發
0 0/5 14 ? 天天下午的 2點到2點59分(整點開始,每隔5分觸發)
0 0/5 14,18 ? 天天下午的 2點到2點59分、18點到18點59分(整點開始,每隔5分觸發)
0 0-5 14 ? 天天下午的 2點到2點05分每分觸發
0 15 10 ? * 6L 每個月最後一週的星期五的10點15分觸發
0 15 10 ? * 6#3 每個月的第三週的星期五開始觸發
咱們能夠經過一些Cron在線工具很是方便的生成,好比http://www.pppet.net/等。
實際上,Quartz和Spring結合是很方便的,無非就是進行一些配置。大概基於2種方式:
第一,普通的類,普通的方法,直接在配置中指定(MethodInvokingJobDetailFactoryBean)。
第二,須要繼承QuartzJobBean,複寫指定方法(executeInternal)便可。
而後,就是一些觸發器、調度器的配置了,這裏再也不展開介紹了,只要弄懂了原生的Quartz的使用,那麼和Spring的結合使用就會很簡單。