Timer 簡單易用,其源碼閱讀起來也非常清晰,本節(jié)我們來仔細(xì)分析一下 Timer 類,來看看 JDK 源碼的編寫者是如何實(shí)現(xiàn)一個穩(wěn)定可靠的簡單調(diào)度器。
Timer 使用
Timer 調(diào)度任務(wù)有一次性調(diào)度和循環(huán)調(diào)度,循環(huán)調(diào)度有分為固定速率調(diào)度(fixRate)和固定時延調(diào)度(fixDelay)。固定速率就好比你今天加班到很晚,但是到了第二天還必須準(zhǔn)點(diǎn)到公司上班,如果你一不小心加班到了第二天早上 9 點(diǎn),你就連休息的時間都沒有了。而固定時延的意思是你必須睡夠 8 個小時再過來上班,如果你加班到凌晨 6 點(diǎn),那就可以下午過來上班了。固定速率強(qiáng)調(diào)準(zhǔn)點(diǎn),固定時延強(qiáng)調(diào)間隔。
如果你有一個任務(wù)必須每天準(zhǔn)點(diǎn)調(diào)度,那就應(yīng)該使用固定速率調(diào)度,并且要確保每個任務(wù)執(zhí)行時間不要太長,千萬別超過了第二天這個點(diǎn)。如果你有一個任務(wù)需要每隔幾分鐘跑一次,那就使用固定時延調(diào)度,它不是很在乎你的單個任務(wù)要跑多長時間。
內(nèi)部結(jié)構(gòu)
Timer 類里包含一個任務(wù)隊(duì)列和一個異步輪訓(xùn)線程。任務(wù)隊(duì)列里容納了所有待執(zhí)行的任務(wù),所有的任務(wù)將會在這一個異步線程里執(zhí)行,切記任務(wù)的執(zhí)行代碼不可以拋出異常,否則會導(dǎo)致 Timer 線程掛掉,所有的任務(wù)都沒得執(zhí)行了。單個任務(wù)也不易執(zhí)行時間太長,否則會影響任務(wù)調(diào)度在時間上的精準(zhǔn)性。比如你一個任務(wù)跑了太久,其它等著調(diào)度的任務(wù)就一直處于饑餓狀態(tài)得不到調(diào)度。所有任務(wù)的執(zhí)行都是這單一的 TimerThread 線程。
堆排序
Timer 的任務(wù)隊(duì)列 TaskQueue 是一個特殊的隊(duì)列,它內(nèi)部是一個數(shù)組。這個數(shù)組會按照待執(zhí)行時間進(jìn)行堆排序,堆頂元素總是待執(zhí)行時間最小的任務(wù)。輪訓(xùn)線程會每次輪訓(xùn)出時間點(diǎn)最近的并且到點(diǎn)的任務(wù)來執(zhí)行。數(shù)組會自動擴(kuò)容,如果任務(wù)非常多。
任意線程都可以通過 Timer.schedule 方法將任務(wù)加入 TaskQueue,但是 TaskQueue 又并不是線程安全的數(shù)據(jù)結(jié)構(gòu)。所在每次修改 TaskQueue 時都需要加鎖。
任務(wù)狀態(tài)
TimerTask 有 4 個狀態(tài),VIRGIN 是默認(rèn)狀態(tài),剛剛實(shí)例化還沒有被調(diào)度。SCHEDULED 表示已經(jīng)將任務(wù)塞進(jìn) TaskQueue 等待被執(zhí)行。EXECUTED 表示任務(wù)已經(jīng)執(zhí)行完成。CANCELLED 表示任務(wù)被取消了,還沒來得及執(zhí)行就被人為取消了。
對于一個循環(huán)任務(wù)來說,它不存在 EXECUTED 狀態(tài),因?yàn)樗看蝿倓倛?zhí)行完成,就被重新調(diào)度了。EXECUTED 狀態(tài)僅僅存在于一次性任務(wù),而且這個狀態(tài)其實(shí)并不是表示任務(wù)已經(jīng)執(zhí)行完成,它是指已經(jīng)從任務(wù)隊(duì)列里摘出來了,馬上就要執(zhí)行。
任務(wù)間隔字段 period 比較特殊,當(dāng)使用固定速率時,period 為正值,當(dāng)使用固定間隔時,period 為負(fù)值,當(dāng)任務(wù)是一次性時,period 為零。下面是循環(huán)任務(wù)的下次調(diào)度時間設(shè)定
對于固定速率來說,如果任務(wù)執(zhí)行時間太長超出了間隔,那么它可能會持續(xù)霸占任務(wù)隊(duì)列,因?yàn)樗恼{(diào)度時間將總是低于 currentTime,排在堆頂,每次輪訓(xùn)取出來的都是它。運(yùn)行完畢后,重新調(diào)度這個任務(wù),它的時間依舊趕不上。持續(xù)下去你會看到這個任務(wù)的調(diào)度時間遠(yuǎn)遠(yuǎn)落后于當(dāng)前時間,而其它任務(wù)可能會徹底餓死。這就是為什么一定要特別注意固定速率的循環(huán)任務(wù)運(yùn)行時間不宜過長。
任務(wù)鎖
Timer 的任務(wù)支持取消操作,取消任務(wù)的線程和執(zhí)行任務(wù)的線程極有可能不是一個線程。有可能任務(wù)正在執(zhí)行中,結(jié)果另一個線程表示要取消任務(wù)。這時候 Timer 是如何處理的呢?在 TimerTask 類里看到了一把鎖。當(dāng)任務(wù)屬性需要修改的時候,都會加鎖。
在任務(wù)運(yùn)行之前會檢查任務(wù)是不是已經(jīng)被取消了,如果取消了,就從隊(duì)列中移除。一旦任務(wù)開始運(yùn)行 run(),對于單次任務(wù)來說它就無法被取消了,而循環(huán)任務(wù)將不會繼續(xù)下次調(diào)度。如果任務(wù)沒有機(jī)會得到執(zhí)行(時間設(shè)置的太長),那么即使這個任務(wù)被取消了,它也會一直持續(xù)躺在任務(wù)隊(duì)列中。設(shè)想如果你調(diào)度了一系列久遠(yuǎn)的任務(wù),然后都取消了,這可能會成為一個內(nèi)存泄露點(diǎn)。所以 Timer 還單獨(dú)提供了一個 purge() 方法可以一次性清空所有的已取消的任務(wù)。