java任務調度之Timer定時器


定時器相信大家都不陌生,平時使用定時器就像使用鬧鍾一樣,我們可以在固定的時間做某件事,也可以在固定的時間段重復做某件事,今天就來分析一下java中自帶的定時任務器Timer。

一、Timer基本使用

在Java中為我們提供了Timer來實現定時任務,當然現在還有很多定時任務框架,比如說Spring、QuartZ、Linux Cron等等,而且性能也更加優越。但是我們想要深入的學習就必須先從最簡單的開始。

在Timer定時任務中,最主要涉及到了兩個類:Timer和TimerTask。他們倆的關系也特別容易理解,TimerTask把我們得業務邏輯寫好之后,然后使用Timer定時執行就OK了。我們來看一個最基本的案例:

這就是我們的TimerTask,我們單獨寫成類時候需要去繼承TimerTask。然后呢我們寫好了之后就可以使用Timer來執行了。

指定的流程很簡單:

(1)第一步:創建一個Timer。

(2)第二步:創建一個TimerTask。

(3)第三步:使用Timer執行TimerTask。

其中第三步無疑是我們目前最關心的,也就是timer.schedule(myTask, 2000L, 1000L)。他的意思是myTask在兩秒鍾之后開始第一次執行,然后每隔一秒執行一次。這只是最基本的用法。就體現了Timer定時執行的流程。當然java中Timer還為我們提供了很多其他的方法。對此就有必要深入其源碼看看了。

二、Timer源碼分析

對於一個類的源碼分析,我一貫的思路就是先從參數開始,然后構造方法,最后就是常用方法。下面我們就按照這個思路開始今天的源碼分析,在這里基於jdk1.8。先給出一張整體類圖:

1、參數

Timer的源碼中為我們提供了兩個最主要的參數TaskQueue和TimerThread。

上面的代碼大概意思是這樣的:

(1)TaskQueue:這是一個最小堆,它存放該Timer的所有TimerTask。

(2)TimerThread:執行TaskQueue中的任務,執行完從任務隊列中移除。

所以上面這兩個參數其實是配合着使用的,那這個TaskQueue是如何存放的呢?在這里我們不妨跟進去看看。

在這里我們只給出了一部分源碼,不過這一部分是整個思想原理最核心的,上面英文的大概意思是;TaskQueue是一個平衡二叉堆,具有最小 nextExecutionTime 的 TimerTask 在隊列中為 queue[1] ,也就是堆中的根節點。第 n 個位置 queue[n] 的子節點分別在 queue[2n] 和 queue[2n+1] 。不了解二叉堆的話,可以看看數據結構。

也就是說TimerTask 在堆中的位置其實是通過nextExecutionTime 來決定的。nextExecutionTime 越小,那么在堆中的位置越靠近根,越有可能先被執行。而nextExecutionTime意思就是下一次執行開始的時間。

還有一個TimerTask數組,默認大小是128個。

2、構造方法

構造方法就比較簡單了,這里一共有四個:

(1)第一個:默認構造方法。

(2)第二個:在構造器中指定是否是守護線程。

(3)第三個:帶有名字的構造方法。

(3)第四個:不僅帶名字,還指定是否是守護線程。

不過我們需要注意一點的是,Timer在構造完成之后會啟動一個后台線程用於執行TaskQueue里面的TimerTask 。

3、定時任務方法

在一開始我們提到,我們不僅可以在指定的時間執行某些任務,還可以在一段時間之后執行。我們對這些方法進行總結一下:

(1)schedule(task,time) 在時間等於或超過time的時候執行且只執行一次task,這個time表示的是例如2019年11月11日上午11點11分11秒。指的是時刻。

(2)schedule(task,time,period)

在時間等於或超過time的時候首次執行task,之后每隔period毫秒重復執行一次task 。這個time和上一個一樣。

(3)schedule(task, delay)

在delay時間之后,執行且只執行一次task。這個delay表示的是延遲時間,比如說三秒后執行。

(4)schedule(task,delay,period)

在delay時間之后,開始首次執行task,之后每隔period毫秒重復執行一次task ,這個delay和上面的一樣。

我們不如來看看源碼:

這四個方法都執行了同一個方法sched,所以我們要弄清楚原理,就必須要再跟進去看看:

上面的代碼我們來分析一下,最上面的if就是排除一下異常情況,最核心的就是synchronized里面的代碼。首先將任務添加到隊列中,然后根據nextExecutionTime調整隊列。

添加任務add(task):

維護最小堆:

上面就是Timer中如何執行的定時任務核心,但是還有一個方法,也是執行定時任務的。叫scheduleAtFixedRate

下面我們來分析一下,然后比較和上面的不同。

4、scheduleAtFixedRate方法

這個方法有兩個:

(1)scheduleAtFixedRate(task, time, period)

在時間等於或超過time的時候首次執行task,之后每隔period毫秒重復執行一次task 。這個time表示的是例如2019年11月11日上午11點11分11秒。指的是時刻。

(2)scheduleAtFixedRate(task, delay, period)

在delay時間之后,開始首次執行task,之后每隔period毫秒重復執行一次task ,這個delay表示的是延遲時間,比如說三秒后執行。

既然上面都已經有了4個定時器,為什么這里還要再增加幾個呢?我們來分析一下他們的區別:

分兩種情況: ① 首次計划執行的時間 schedule:如果第一次執行時間被delay了,隨后的執行時間按照上一次實際執行完的時間點進行計算 。scheduleAtFixedRate:如果第一次執行時間被delay了,隨后的執行時間按上一次開始的時間進行計算,並且為了趕上進度會多次執行任務,因此TimerTask中的執行體需要考慮同步。

②任務執行所需時間 schedule方法:下一次執行時間會不斷延后,因此參照的是上一次執行完成的時間點。scheduleAtFixedRate方法:下一次執行時間不會延后,因此存在並發性。我們可以看一下圖:

5、其他方法

我們已經明白了如何創建Timer和執行定時任務,如果在執行的時候我們突然改變主意,想要取消怎么辦呢?這里Timer當然為我們提供了。

(1)cancel:取消此計時器任務。

(2)scheduledExecutionTime():返回此任務最近實際執行的安排執行時間。

6、任務調度

任務調度也就是說我們的線程如何去執行這些任務。其實在TimerThread調用了run來執行,我們看一下源碼。

也就是說其實真正執行任務調度的是mainLoop(),synchronized代碼塊只是為了確保在執行完之后能夠移除這個task。

而這個mainLoop方法的思想很簡單,就是拿出任務隊列中的第一個任務,如果執行時間還沒有到,則繼續等待,否則立即執行。源碼在這里就不再給出了。

三、Timer缺陷

上面從源碼的角度分析了一下Timer,因為用法很簡單,主要是源碼分析。說了這么多,Timer還是有一定的缺陷的,

1、Timer管理延時任務的缺陷

Timer在執行定時任務時只會創建一個線程,所以如果存在多個任務,且任務時間過長,超過了兩個任務的間隔時間,會發生一些缺陷。我們看一個例子:

這個例子中的功能是這樣的,第一個任務在1秒鍾之后開始執行,第二個任務在2秒鍾之后開始執行。

第一步:定義兩個TimerTask

還有一個:

第二步:我們測試一下:

我們在上面的Task1中會發現,任務2不是應該在32秒的時候執行嘛,怎么會在4秒鍾之后才執行。究其原因是任務1執行了3秒,但是線程只有一個,所以只能先把任務1執行完才去執行任務2。這就是其缺陷之一。

2、Timer當任務拋出異常時的缺陷

這個缺陷的意思是,其中有一個任務拋出了RuntimeException,那么所有的任務都會停止執行。這個演示起來很簡單。

第一步:聲明幾個定時任務

第二步:測試

我們來看一下結果:

正是Timer有很多的缺陷,所以出現了Timer的替代品ScheduledExecutorService,用來解決上面出現的問題。而且也出現了很多優秀的框架。具體的我會在后續文章中介紹。

OK,今天的文章到這,歡迎批評指正。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM