線程池深入解析——定時任務執行流程

  注:java源碼分析部分如無特殊說明均基於 java8 版本。

  注:本文基於ScheduledThreadPoolExecutor定時線程池類。

  簡介

  前面我們一起學習了普通任務、未來任務的執行流程,今天我們再來學習一種新的任務——定時任務。

  定時任務是我們經常會用到的一種任務,它表示在未來某個時刻執行,或者未來按照某種規則重複執行的任務。

  問題

  (1)如何保證任務是在未來某個時刻才被執行?

  (2)如何保證任務按照某種規則重複執行?

  來個栗子

  創建一個定時線程池,用它來跑四種不同的定時任務。

  定時任務總體分爲四種:

  (1)未來執行一次的任務,無返回值;

  (2)未來執行一次的任務,有返回值;

  (3)未來按固定頻率重複執行的任務;

  (4)未來按固定延時重複執行的任務;

  本文主要以第三種爲例進行源碼解析。

  scheduleAtFixedRate()方法

  提交一個按固定頻率執行的任務。

  可以看到,這裏的處理跟未來任務類似,都是裝飾成另一個任務,再拿去執行,不同的是這裏交給了delayedExecute()方法去執行,這個方法是幹嘛的呢?

delayedExecute()方法

延時執行。

  到這裏就結束了?!

  實際上,這裏只是控制任務能不能被執行,真正執行任務的地方在任務的run()方法中。

  還記得上面的任務被裝飾成了ScheduledFutureTask類的實例嗎?所以,我們只要看ScheduledFutureTask的run()方法就可以了。

  ScheduledFutureTask類的run()方法

  定時任務執行的地方。

  可以看到,對於重複性任務,先調用FutureTask的runAndReset()方法,再設置下次執行的時間,最後再調用reExecutePeriodic()方法。

  FutureTask的runAndReset()方法與run()方法類似,只是其任務運行完畢後不會把狀態修改爲NORMAL,有興趣的同學點進源碼看看。

  再來看看reExecutePeriodic()方法。

  到這裏是不是豁然開朗了,原來定時線程池執行重複任務是在任務執行完畢後,又把任務扔回了任務隊列中。

  重複性的問題解決了,那麼,它是怎麼控制任務在某個時刻執行的呢?

  OK,這就輪到我們的延時隊列登場了。

  DelayedWorkQueue內部類

  我們知道,線程池執行任務時需要從任務隊列中拿任務,而普通的任務隊列,如果裏面有任務就直接拿出來了,但是延時隊列不一樣,它裏面的任務,如果沒有到時間也是拿不出來的,這也是前面分析中一上來就把任務扔進隊列且創建Worker沒有傳入firstTask的原因。

  我們這裏只拿一個take()方法出來分析。

  大致的原理是,利用堆的特性獲取最快到時間的任務,即堆頂的任務:

  (1)如果堆頂的任務到時間了,就讓它從隊列中了隊;

  (2)如果堆頂的任務還沒到時間,就看它還有多久到時間,利用條件鎖等待這段時間,待時間到了後重新走(1)的判斷;

  這樣就解決了可以在指定時間後執行任務。

  其它

  其實,ScheduledThreadPoolExecutor也是可以使用execute()或者submit()提交任務的,只不過它們會被當成0延時的任務來執行一次。

  總結

  實現定時任務有兩個問題要解決,分別是指定未來某個時刻執行任務、重複執行。

  (1)指定某個時刻執行任務,是通過延時隊列的特性來解決的;

  (2)重複執行,是通過在任務執行後再次把任務加入到隊列中來解決的。

  彩蛋

  到這裏基本上普通的線程池的源碼解析就結束了,這種線程池是比較經典的實現方式,整體上來說,效率相對不是特別高,因爲所有的工作線程共用同一個隊列,每次從隊列中取任務都要加鎖解鎖操作。

  那麼,能不能給每個工作線程配備一個任務隊列呢,在提交任務的時候就把任務分配給指定的工作線程,這樣在取任務的時候就不需要頻繁的加鎖解鎖了。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章