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