文章目錄
首先我們需要了解線程池在什麼情況下會自動關閉。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) { ... ... }
這裏我們只關心與線程存活狀態最緊密相關的兩個參數,也就是corePoolSize
和keepAliveTime
,上述代碼塊也包含了這兩個參數的源碼註釋和中文翻譯。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雖然可以實現線程池的自動關閉,但也存在一些弊端,此話怎講,先來看一下線程池的執行流程:
線程池執行流程圖
如圖所示,當有新的任務到來時,程序會先判斷線程池當前線程數是否達到corePoolSize
(核心線程數),沒達到則創建線程執行任務,達到則嘗試將任務放入任務隊列 (workQueue
)。如果將corePoolSize
設置爲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. 結語
以上就是線程池可以自動關閉的兩種情況,而且梳理了詳細的線程池執行流程,相信你看完本文一定會有所收穫。不過話又說回來,可自動關閉的線程池的實際應用場景並不多,更多時候需要我們手動關閉。下一篇文章我們就來聊聊如何手動關閉線程池,敬請期待!