Java 線程池會自動關閉嗎|轉


Java 線程池會自動關閉嗎|轉

  首先我們需要了解線程池在什么情況下會自動關閉。ThreadPoolExecutor 類(這是我們最常用的線程池實現類)的源碼注釋中有這么一句話:

A pool that is no longer referenced in a program and has no remaining threads will be shutdown automatically.

沒有引用指向且沒有剩余線程的線程池將會自動關閉。

那么什么情況下線程池中會沒有剩余線程呢?先來看一下 ThreadPoolExecutor 參數最全的構造方法:

/** * @param corePoolSize the number of threads to keep in the pool, even * if they are idle, unless {@code allowCoreThreadTimeOut} is set * 核心線程數:即使是空閑狀態也可以在線程池存活的線程數量,除非 * allowCoreThreadTimeOut 設置為 true。 * @param keepAliveTime when the number of threads is greater than * the core, this is the maximum time that excess idle threads * will wait for new tasks before terminating. * 存活時間:對於超出核心線程數的線程,空閑時間一旦達到存活時間,就會被銷毀。 */ public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { ... ... } 

  這里我們只關心與線程存活狀態最緊密相關的兩個參數,也就是corePoolSizekeepAliveTime,上述代碼塊也包含了這兩個參數的源碼注釋和中文翻譯。keepAliveTime參數指定了非核心線程的存活時間,非核心線程的空閑時間一旦達到這個值,就會被銷毀,而核心線程則會繼續存活,只要有線程存活,線程池也就不會自動關閉。聰明的你一定會想到,如果把corePoolSize設置為0,再給keepAliveTime指定一個值的話,那么線程池在空閑一段時間之后,不就可以自動關閉了嗎?沒錯,這就是線程池自動關閉的第一種情況。

1. 核心線程數為 0 並指定線程存活時間

1.1. 手動創建線程池

代碼示例:

public class ThreadPoolTest { public static void main(String[] args) { // 重點關注 corePoolSize 和 keepAliveTime,其他參數不重要 ThreadPoolExecutor executor = new ThreadPoolExecutor(0, 5, 30L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(15)); for (int i = 0; i < 20; i++) { executor.execute(() -> { // 簡單地打印當前線程名稱 System.out.println(Thread.currentThread().getName()); }); } } } 

控制台輸出結果

# 線程打印開始 ... ... pool-1-thread-2 pool-1-thread-3 pool-1-thread-4 pool-1-thread-5 pool-1-thread-1 # 打印結束,程序等待30s后正常退出 Process finished with exit code 0 # 小知識:exit code 0 說明程序是正常退出,非強行中斷或異常退出 

  通過以上代碼和運行結果可以得知,在corePoolSize為0且keepAliveTime設置為 60s 的情況下,如果任務執行完畢又沒有新的任務到來,線程池里的線程都將消亡,而且沒有核心線程阻止線程池關閉,因此線程池也將隨之自動關閉。

  而如果將corePoolSize設置為大於0的數字,再運行以上代碼,那么線程池將一直處於等待狀態而不能關閉,因為核心線程不受keepAliveTime控制,所以會一直存活,程序也將一直不能結束。運行效果如下 (corePoolSize設置為5,其他參數不變):

# 線程打印開始 ... ... pool-1-thread-5 pool-1-thread-1 pool-1-thread-3 pool-1-thread-4 pool-1-thread-2 # 打印結束,但程序無法結束 

2.2 Executors.newCachedThrteadPool() 創建線程池

  Executors 是 JDK 自帶的線程池框架類,包含多個創建不同類型線程池的方法,而其中的newCachedThrteadPool()方法也將核心線程數設置為了0並指定了線程存活時間,所以也可以自動關閉。其源碼如下:

public class Executors { ... ... public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); } ... ... } 

  如果用這個線程池運行上面的代碼,程序也會自動退出,效果如下

# 線程打印開始 ... ... pool-1-thread-7 pool-1-thread-5 pool-1-thread-4 pool-1-thread-1 pool-1-thread-9 # 打印結束,程序等待60s后退出 Process finished with exit code 0 

2. 通過 allowCoreThreadTimeOut 控制核心線程存活時間

  通過將核心線程數設置為0雖然可以實現線程池的自動關閉,但也存在一些弊端,新到來的任務若發現沒有活躍線程,則會優先被放入任務隊列,然后等待被處理,這顯然會影響程序的執行效率。那你可能要問了,有沒有其他的方法來自己實現可自動關閉的線程池呢?答案是肯定的,從 JDK 1.6 開始,ThreadPoolExecutor 類新增了一個allowCoreThreadTimeOut字段:

/** * If false (default), core threads stay alive even when idle. * If true, core threads use keepAliveTime to time out waiting * for work. * 默認為false,核心線程處於空閑狀態也可一直存活 * 如果設置為true,核心線程的存活狀態將受keepAliveTime控制,超時將被銷毀 */ private volatile boolean allowCoreThreadTimeOut; 

  這個字段值默認為false,可使用allowCoreThreadTimeOut()方法對其進行設置,如果設置為 true,那么核心線程數也將受keepAliveTime控制,此方法源碼如下:

public void allowCoreThreadTimeOut(boolean value) { // 核心線程存活時間必須大於0,一旦開啟,keepAliveTime 也必須大於0 if (value && keepAliveTime <= 0) throw new IllegalArgumentException("Core threads must have nonzero keep alive times"); // 將 allowCoreThreadTimeOut 值設為傳入的參數值 if (value != allowCoreThreadTimeOut) { allowCoreThreadTimeOut = value; // 開啟后,清理所有的超時空閑線程,包括核心線程 if (value) interruptIdleWorkers(); } } 

  既然如此,接下來我們就借助這個方法實現一個可自動關閉且核心線程數不為0的線程池,這里直接在第一個程序的基礎上進行改進:

public class ThreadPoolTest { public static void main(String[] args) { // 這里把corePoolSize設為5,keepAliveTime保持不變 ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 5, 30L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(15)); // 允許核心線程超時銷毀 executor.allowCoreThreadTimeOut(true); for (int i = 0; i < 20; i++) { executor.execute(() -> { System.out.println(Thread.currentThread().getName()); }); } } } 

  運行結果:

# 線程打印開始 ... ... pool-1-thread-1 pool-1-thread-2 pool-1-thread-3 pool-1-thread-4 pool-1-thread-5 # 打印結束,程序等待30s后退出 Process finished with exit code 0 

  可以看到,程序在打印結束后等待了30s,然后自行退出,說明線程池已自動關閉,也就是allowCoreThreadTimeOut()方法發揮了作用。這樣,我們就實現了可自動關閉且核心線程數不為0的線程池。

3. 超詳細的線程池執行流程圖

  讓我們再來梳理一下更完整的線程池執行流程:


詳細的線程池執行流程圖

4. 小結

  以上就是線程池可以自動關閉的兩種情況,而且梳理了詳細的線程池執行流程,相信你看完本文一定會有所收獲。不過話又說回來,可自動關閉的線程池的實際應用場景並不多,更多時候需要我們手動關閉。在執行完任務后調用ExecutorService的shutdown()方法,具體測試用例請參考《Java 自定義線程池的線程工廠》一文中優雅的自定義線程工廠這一節。

Reference


免責聲明!

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



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