1. 程式人生 > >一文教你安全的關閉執行緒池

一文教你安全的關閉執行緒池

上篇文章 ShutdownHook- Java 優雅停機解決方案 提到應用停機時需要釋放資源,關閉連線。對於一些定時任務或者網路請求服務將會使用執行緒池,當應用停機時需要正確安全的關閉執行緒池,如果處理不當,可能造成資料丟失,業務請求結果不正確等問題。

關閉執行緒池我們可以選擇什麼都不做,JVM 關閉時自然的會清除執行緒池物件。當然這麼做,存在很大的弊端,執行緒池中正在執行執行的執行緒以及佇列中還未執行任務將會變得極不可控。所以我們需要想辦法控制到這些未執行的任務以及正在執行的執行緒。

執行緒池 API 提供兩個主動關閉的方法 ThreadPoolExecutor#shutdownNowThreadPoolExecutor#shutdown

,這兩個方法都可以用於關閉執行緒池,但是具體效果卻不太一樣。

執行緒池的狀態

在說執行緒池關閉方法之前,我們先了解執行緒池狀態。

執行緒池狀態關係圖如下:

從上圖我們看到執行緒池總共存在 5 種狀態,分別為:

  • RUNNING:執行緒池建立之後的初始狀態,這種狀態下可以執行任務。
  • SHUTDOWN:該狀態下執行緒池不再接受新任務,但是會將工作佇列中的任務執行結束。
  • STOP: 該狀態下執行緒池不再接受新任務,但是不會處理工作佇列中的任務,並且將會中斷執行緒。
  • TIDYING:該狀態下所有任務都已終止,將會執行 terminated() 鉤子方法。
  • TERMINATED:執行完 terminated()
    鉤子方法之後。

當我們執行 ThreadPoolExecutor#shutdown 方法將會使執行緒池狀態從 RUNNING 轉變為 SHUTDOWN。而呼叫 ThreadPoolExecutor#shutdownNow 之後執行緒池狀態將會從 RUNNING 轉變為 STOP。從上面的圖上還可以看到,當執行緒池處於 SHUTDOWN,我們還是可以繼續呼叫 ThreadPoolExecutor#shutdownNow 方法,將其狀態轉變為 STOP 。

ThreadPoolExecutor#shutdown

上面我們知道執行緒池狀態,這裡先說說 shutdown 方法。shutdown 方法原始碼比較簡單,能比較直觀理解其呼叫邏輯。

shutdown 方法原始碼:

    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 ,執行緒將會終止。

工作流程圖如下:

當執行緒池處於第二步時,執行緒將會使用 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

相關推薦

文教安全關閉執行

上篇文章 ShutdownHook- Java 優雅停機解決方案 提到應用停機時需要釋放資源,關閉連線。對於一些定時任務或者網路請求服務將會使用執行緒池,當應用停機時需要正確安全的關閉執行緒池,如果處理不當,可能造成資料丟失,業務請求結果不正確等問題。 關閉執行緒池我們可以選擇什麼都不做,JVM 關閉時自

關閉執行

關閉執行緒池 程式碼如下: public class ClosePoolTest { public static final Logger LOG = LoggerFactory.getLogger(ClosePoolTest.class); public static

Java高併發程式設計(十):Java中執行

在開發過程中,合理地使用執行緒池能夠帶來3個好處。 降低資源消耗。通過重複利用已建立的執行緒降低執行緒建立和銷燬造成的消耗。 提高響應速度。當任務到達時,任務可以不需要等到執行緒建立就能立即執行。 提高執行緒的可管理性。執行緒是稀缺資源,如果無限制地建立

使用執行時候當程式結束時候記得呼叫shutdown關閉執行

在一個方法裡面建立了一個執行緒池,然後做一個分片上傳檔案的任務,有多少個分片就有多少個執行緒使用newSingleThreadExecutor 建立,忘了呼叫shutdown比執行緒池本來以為就算沒關閉也會被垃圾回收,因為執行緒池是方法裡面建立的區域性變數但是非守護執行緒是不

java執行關閉執行中的執行

如果執行緒經常喜歡去new的話是不對的,你需要一個池子管理。 newCachedThreadPool 這個一個帶快取的執行緒池,是個可以無限大的執行緒池,新建的執行緒放倒這個池子裡,當執行緒停掉了的時候,下個個執行緒進來,可以複用這個執行緒。 newFixe

優雅的關閉執行

@PreDestroy public void destory() { try {

linux下 c中怎麼讓才能安全關閉執行

多執行緒退出有三種方式:(1)執行完成後隱式退出;(2)由執行緒本身顯示呼叫pthread_exit 函式退出;    pthread_exit (void * retval) ; (3)被其他執行緒用pthread_cance函式終止:    pthread_cance

Linux雜談: 實現種簡單實用的執行(C語言)

基本功能 1. 實現一個執行緒的佇列,佇列中的執行緒啟動後不再釋放; 2. 沒有任務執行時,執行緒處於pending狀態,等待喚醒,不佔cpu; 3. 當有任務需要執行時,從執行緒佇列中取出一個執行緒執行任務; 4. 任務執行完成後執行緒再次進入pending狀態,等待喚醒;   擴充套件功能 1.

新手看就懂的執行

經過前幾篇文章的學習,大家對多執行緒應該有些瞭解了吧,這裡附上前三篇文章的連結,還沒有看過的小夥伴快去複習吧~~ [多執行緒基礎篇入門](https://mp.weixin.qq.com/s/k23TcOVAkATps38p_TioEQ) [執行緒的生命週期和常用 APIs](https://mp.wei

優雅關閉執行的方案

![](https://img2020.cnblogs.com/other/2024393/202011/2024393-20201123154303134-2142116595.jpg) * 我們經常在專案中使用的執行緒池,但是是否關心過執行緒池的關閉呢,可能很多時候直接再專案中直接建立執行緒池讓它一直運

ScheduledThreadPoolExecutor原始碼分析-知道定時執行是如何實現延遲執行和週期執行的嗎?

Java版本:8u261。 ## 1 簡介 ScheduledThreadPoolExecutor即定時執行緒池,是用來執行延遲任務或週期性任務的。相比於Timer的單執行緒,定時執行緒池在遇到任務丟擲異常的時候不會關閉整個執行緒池,更加健壯(需要提一下的是:ScheduledThreadPoolExec

文帶吃透執行

微信公眾號:[Amos部落格] 內容目錄 TreadPoolexecutor原始碼解析 類關係圖 Executor介面 ExecutorService介面 AbstractExecutorService 成員變數

#篇文章讓瞭解四種執行,學習Java不在困惑

在Java開發中,有時遇到多執行緒的開發時,直接使用Thread操作,對程式的效能和維護上都是一個問題,使用Java提供的執行緒池來操作可以很好的解決問題,於是找了下API看到Java提供四種執行緒池使用,Java通過Executors提供四種執行緒池,分別為: 1、newCachedThrea

次整合spring-amqp後出現執行為正常關閉。導致tomcat無法正常關閉顯示記憶體洩露的問題

起因:因為這幾天閒來無事,所以想著改造下舊專案的訂單自動取消功能,原本是通過定時任務輪詢掃描未支付訂單的,及時性不足並且浪費資料庫io的資源,所以就想用rabbitmq的死信佇列來完成延遲自動取消的功能。於是隨手copy了一段spring-amqp的Java Configur

文讓領悟執行的原理和機制設計—洞虛篇

書接上文,[一文加深你對Java執行緒池的瞭解與使用—築基篇](https://www.cnblogs.com/DMingO/p/13415855.html),本文將從執行緒池內部的最最核心類 **ThreadPoolExecutor** 原始碼中的重要方法入手,也是本文分析的物件,從狀態/任務/執行緒這三個

java併發程式設計一一執行原理分析()

1、併發包 1、CountDownLatch(計數器) CountDownLatch 類位於 java.util.concurrent 包下,利用它可以實現類似於計數器的功能。 比如有一個任務A,它要等待其他4個任務執行完成之後才能執行,此時就可以利用CountDownLatch

java併發學習--執行

關於java中的執行緒池,我一開始覺得就是為了避免頻繁的建立和銷燬執行緒吧,先建立一定量的執行緒,然後再進行復用。但是要具體說一下如何做到的,自己又說不出一個一二三來了,這大概就是自己的學習習慣流於表面,不經常深入的結果吧。所以這裡決定系統的學習一下執行緒池的相關知識。   自己稍微總結了一下,

如何實現自己的執行(不看後悔,看必懂)

首先,在服務啟動的時候,我們可以啟動好幾個執行緒,並用一個容器(如執行緒池)來管理這些執行緒。當請求到來時,可以從池中取一個執行緒出來,執行任務(通常是對請求的響應),當任務結束後,再將這個執行緒放入池中備用;如果請求到來而池中沒有空閒的執行緒,該請求需要排隊等候。最後,當服務關閉時銷燬該池即可

java中常見的執行(不看後悔,看必懂)

Executor介面表示執行緒池,它的execute(Runnable task)方法用來執行Runnable型別的任務,ExecutorService是Executor的子介面,聲明瞭管理執行緒池的一些方法 Java.util.concurrent.Executors類包含了一些靜態

【小家java】Java中的執行真的用對了嗎?(教用正確的姿勢使用執行

相關閱讀 【小家java】java5新特性(簡述十大新特性) 重要一躍 【小家java】java6新特性(簡述十大新特性) 雞肋升級 【小家java】java7新特性(簡述八大新特性) 不溫不火 【小家java】java8新特性(簡述十大新特性) 飽受讚譽 【小家java】java9