一文教你安全的關閉線程池

上篇文章 ShutdownHook- Java 優雅停機解決方案 提到應用停機時須要釋放資源,關閉鏈接。對於一些定時任務或者網絡請求服務將會使用線程池,當應用停機時須要正確安全的關閉線程池,若是處理不當,可能形成數據丟失,業務請求結果不正確等問題。html

關閉線程池咱們能夠選擇什麼都不作,JVM 關閉時天然的會清除線程池對象。固然這麼作,存在很大的弊端,線程池中正在執行執行的線程以及隊列中還未執行任務將會變得極不可控。因此咱們須要想辦法控制到這些未執行的任務以及正在執行的線程。java

線程池 API 提供兩個主動關閉的方法 ThreadPoolExecutor#shutdownNowThreadPoolExecutor#shutdown,這兩個方法均可以用於關閉線程池,可是具體效果卻不太同樣。安全

線程池的狀態

在說線程池關閉方法以前,咱們先了解線程池狀態。網絡

線程池狀態關係圖以下:ide

線程池狀態.png

從上圖咱們看到線程池總共存在 5 種狀態,分別爲:函數

  • RUNNING:線程池建立以後的初始狀態,這種狀態下能夠執行任務。
  • SHUTDOWN:該狀態下線程池再也不接受新任務,可是會將工做隊列中的任務執行結束。
  • STOP: 該狀態下線程池再也不接受新任務,可是不會處理工做隊列中的任務,而且將會中斷線程。
  • TIDYING:該狀態下全部任務都已終止,將會執行 terminated() 鉤子方法。
  • TERMINATED:執行完 terminated() 鉤子方法以後。

當咱們執行 ThreadPoolExecutor#shutdown 方法將會使線程池狀態從 RUNNING 轉變爲 SHUTDOWN。而調用 ThreadPoolExecutor#shutdownNow 以後線程池狀態將會從 RUNNING 轉變爲 STOP。從上面的圖上還能夠看到,當線程池處於 SHUTDOWN,咱們仍是能夠繼續調用 ThreadPoolExecutor#shutdownNow 方法,將其狀態轉變爲 STOPthis

ThreadPoolExecutor#shutdown

上面咱們知道線程池狀態,這裏先說說 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 方法,線程池工做流程以下:

  1. 若是此時線程池中線程數量小於 coreSize,將會新建線程執行提交的任務。
  2. 若是此時線程池線程數量已經大於 coreSize,將會直接把任務加入到隊列中。線程將會從工做隊列中獲取任務執行。
  3. 若是工做隊列已滿,將會繼續新建線程。
  4. 若是工做隊列已滿,且線程數等於 maximumPoolSize,此時將會使用拒絕策略拒絕任務。
  5. 超過 coreSize 數量那部分線程,若是空閒了 keepAliveTime ,線程將會終止。

工做流程圖以下:

線程池執行流程圖.png

當線程池處於第二步時,線程將會使用 workQueue#take 獲取隊頭的任務,而後完成任務。若是工做隊列一直沒任務,因爲隊列爲阻塞隊列,workQueue#take 將會阻塞線程。

ThreadPoolExecutor#shutdownNow

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 異常。

awaitTermination

線程池 shutdownshutdownNow 方法都不會主動等待執行任務的結束,若是須要等到線程池任務執行結束,須要調用 awaitTermination 主動等待任務調用結束。

調用方法以下:

threadPool.shutdown();
        try {
            while (!threadPool.awaitTermination(60,TimeUnit.SECONDS)){
                System.out.println("線程池任務還未執行結束");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

若是線程池任務執行結束,awaitTermination 方法將會返回 true,不然當等待時間超過指定時間後將會返回 false

若是須要使用這種進制,建議在上面的基礎上增長必定重試次數。這個真的很重要!!!

優雅關閉線程池

回顧上面線程池狀態關係圖,咱們能夠知道處於 SHUTDOWN 的狀態下的線程池依舊能夠調用 shutdownNow。因此咱們能夠結合 shutdownshutdownNowawaitTermination ,更加優雅關閉線程池。

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

其餘平臺.png

相關文章
相關標籤/搜索