上篇文章 ShutdownHook- Java 優雅停機解決方案 提到應用停機時須要釋放資源,關閉鏈接。對於一些定時任務或者網絡請求服務將會使用線程池,當應用停機時須要正確安全的關閉線程池,若是處理不當,可能形成數據丟失,業務請求結果不正確等問題。html
關閉線程池咱們能夠選擇什麼都不作,JVM 關閉時天然的會清除線程池對象。固然這麼作,存在很大的弊端,線程池中正在執行執行的線程以及隊列中還未執行任務將會變得極不可控。因此咱們須要想辦法控制到這些未執行的任務以及正在執行的線程。java
線程池 API 提供兩個主動關閉的方法 ThreadPoolExecutor#shutdownNow
與 ThreadPoolExecutor#shutdown
,這兩個方法均可以用於關閉線程池,可是具體效果卻不太同樣。安全
在說線程池關閉方法以前,咱們先了解線程池狀態。網絡
線程池狀態關係圖以下:ide
從上圖咱們看到線程池總共存在 5 種狀態,分別爲:函數
terminated()
鉤子方法。terminated()
鉤子方法以後。當咱們執行 ThreadPoolExecutor#shutdown
方法將會使線程池狀態從 RUNNING 轉變爲 SHUTDOWN。而調用 ThreadPoolExecutor#shutdownNow
以後線程池狀態將會從 RUNNING 轉變爲 STOP。從上面的圖上還能夠看到,當線程池處於 SHUTDOWN,咱們仍是能夠繼續調用 ThreadPoolExecutor#shutdownNow
方法,將其狀態轉變爲 STOP 。this
上面咱們知道線程池狀態,這裏先說說 shutdown
方法。shutdown
方法源碼比較簡單,能比較直觀理解其調用邏輯。idea
shutdown
方法源碼:spa
public void shutdown() { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { // 檢查權限 checkShutdownAccess(); // 設置線程池狀態 advanceRunState(SHUTDOWN); // 中斷空閒線程 interruptIdleWorkers(); // 鉤子函數,主要用於清理一些資源 onShutdown(); } finally { mainLock.unlock(); } tryTerminate(); }
shutdown
方法首先加鎖,其次先檢查系統安裝狀態。接着就會將線程池狀態變爲 SHUTDOWN,在這以後線程池再也不接受提交的新任務。此時若是還繼續往線程池提交任務,將會使用線程池拒絕策略響應,默認狀況下將會使用 ThreadPoolExecutor.AbortPolicy
,拋出 RejectedExecutionException
異常。線程
interruptIdleWorkers
方法只會中斷空閒的線程,不會中斷正在執行任務的的線程。空閒的線程將會阻塞在線程池的阻塞隊列上。
線程池構造參數須要指定 coreSize(核心線程池數量),maximumPoolSize(最大的線程池數量),keepAliveTime(多餘空閒線程等待時間),unit(時間單位),workQueue(阻塞隊列)。
當調用線程池的 execute
方法,線程池工做流程以下:
工做流程圖以下:
當線程池處於第二步時,線程將會使用 workQueue#take
獲取隊頭的任務,而後完成任務。若是工做隊列一直沒任務,因爲隊列爲阻塞隊列,workQueue#take
將會阻塞線程。
ThreadPoolExecutor#shutdownNow
源碼以下:
public List<Runnable> shutdownNow() { List<Runnable> tasks; final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { // 檢查狀態 checkShutdownAccess(); // 將線程池狀態變爲 STOP advanceRunState(STOP); // 中斷全部線程,包括工做線程以及空閒線程 interruptWorkers(); // 丟棄工做隊列中存量任務 tasks = drainQueue(); } finally { mainLock.unlock(); } tryTerminate(); return tasks; }
shutdownNow
方法將會把線程池狀態設置爲 STOP,而後中斷全部線程,最後取出工做隊列中全部未完成的任務返回給調用者。
對比 shutdown
方法,shutdownNow
方法比較粗暴,直接中斷工做線程。不過這裏須要注意,中斷線程並不表明線程馬上結束。這裏須要線程主動配合線程中斷響應。
線程中斷機制:
thread#interrupt
只是設置一箇中斷標誌,不會當即中斷正常的線程。若是想讓中斷當即生效,必須在線程 內調用Thread.interrupted()
判斷線程的中斷狀態。
對於阻塞的線程,調用中斷時,線程將會馬上退出阻塞狀態並拋出InterruptedException
異常。因此對於阻塞線程須要正確處理InterruptedException
異常。
線程池 shutdown
與 shutdownNow
方法都不會主動等待執行任務的結束,若是須要等到線程池任務執行結束,須要調用 awaitTermination
主動等待任務調用結束。
調用方法以下:
threadPool.shutdown(); try { while (!threadPool.awaitTermination(60,TimeUnit.SECONDS)){ System.out.println("線程池任務還未執行結束"); } } catch (InterruptedException e) { e.printStackTrace(); }
若是線程池任務執行結束,awaitTermination
方法將會返回 true
,不然當等待時間超過指定時間後將會返回 false
。
若是須要使用這種進制,建議在上面的基礎上增長必定重試次數。這個真的很重要!!!
回顧上面線程池狀態關係圖,咱們能夠知道處於 SHUTDOWN 的狀態下的線程池依舊能夠調用 shutdownNow
。因此咱們能夠結合 shutdown
, shutdownNow
,awaitTermination
,更加優雅關閉線程池。
threadPool.shutdown(); // Disable new tasks from being submitted // 設定最大重試次數 try { // 等待 60 s if (!threadPool.awaitTermination(60, TimeUnit.SECONDS)) { // 調用 shutdownNow 取消正在執行的任務 threadPool.shutdownNow(); // 再次等待 60 s,若是還未結束,能夠再次嘗試,或則直接放棄 if (!threadPool.awaitTermination(60, TimeUnit.SECONDS)) System.err.println("線程池任務未正常執行結束"); } } catch (InterruptedException ie) { // 從新調用 shutdownNow threadPool.shutdownNow(); }
文章首發於 studyidea.cn/close..
歡迎關注個人公衆號:程序通事,得到平常乾貨推送。若是您對個人專題內容感興趣,也能夠關注個人博客: studyidea.cn