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