首先我們需要了解線程池在什么情況下會(huì)自動(dòng)關(guān)閉澜公。ThreadPoolExecutor 類(這是我們最常用的線程池實(shí)現(xiàn)類)的源碼注釋中有這么一句話:
A pool that is no longer referenced in a program and has no remaining threads will be shutdown automatically.
沒有引用指向且沒有剩余線程的線程池將會(huì)自動(dòng)關(guān)閉名眉。
那么什么情況下線程池中會(huì)沒有剩余線程呢?先來(lái)看一下 ThreadPoolExecutor 參數(shù)最全的構(gòu)造方法:
/**
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* 核心線程數(shù):即使是空閑狀態(tài)也可以在線程池存活的線程數(shù)量磕道,除非
* allowCoreThreadTimeOut 設(shè)置為 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.
* 存活時(shí)間:對(duì)于超出核心線程數(shù)的線程行冰,空閑時(shí)間一旦達(dá)到存活時(shí)間溺蕉,就會(huì)被銷毀。
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) { ... ... }
這里我們只關(guān)心與線程存活狀態(tài)最緊密相關(guān)的兩個(gè)參數(shù)悼做,也就是corePoolSize
和keepAliveTime
疯特,上述代碼塊也包含了這兩個(gè)參數(shù)的源碼注釋和中文翻譯。keepAliveTime
參數(shù)指定了非核心線程的存活時(shí)間肛走,非核心線程的空閑時(shí)間一旦達(dá)到這個(gè)值漓雅,就會(huì)被銷毀,而核心線程則會(huì)繼續(xù)存活朽色,只要有線程存活邻吞,線程池也就不會(huì)自動(dòng)關(guān)閉。聰明的你一定會(huì)想到葫男,如果把corePoolSize
設(shè)置為0抱冷,再給keepAliveTime
指定一個(gè)值的話,那么線程池在空閑一段時(shí)間之后腾誉,不就可以自動(dòng)關(guān)閉了嗎徘层?沒錯(cuò),這就是線程池自動(dòng)關(guān)閉的第一種情況利职。
1. 線程池自動(dòng)關(guān)閉的情況一:核心線程數(shù)為 0 并指定線程存活時(shí)間
1.1. 手動(dòng)創(chuàng)建線程池
代碼示例:
public class ThreadPoolTest {
public static void main(String[] args) {
// 重點(diǎn)關(guān)注 corePoolSize 和 keepAliveTime趣效,其他參數(shù)不重要
ThreadPoolExecutor executor = new ThreadPoolExecutor(0, 5,
30L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(15));
for (int i = 0; i < 20; i++) {
executor.execute(() -> {
// 簡(jiǎn)單地打印當(dāng)前線程名稱
System.out.println(Thread.currentThread().getName());
});
}
}
}
控制臺(tái)輸出結(jié)果
# 線程打印開始
... ...
pool-1-thread-2
pool-1-thread-3
pool-1-thread-4
pool-1-thread-5
pool-1-thread-1
# 打印結(jié)束,程序等待30s后正常退出
Process finished with exit code 0 # 小知識(shí):exit code 0 說(shuō)明程序是正常退出猪贪,非強(qiáng)行中斷或異常退出
通過以上代碼和運(yùn)行結(jié)果可以得知跷敬,在corePoolSize
為0且keepAliveTime
設(shè)置為 60s 的情況下,如果任務(wù)執(zhí)行完畢又沒有新的任務(wù)到來(lái)热押,線程池里的線程都將消亡西傀,而且沒有核心線程阻止線程池關(guān)閉,因此線程池也將隨之自動(dòng)關(guān)閉桶癣。
而如果將corePoolSize
設(shè)置為大于0的數(shù)字拥褂,再運(yùn)行以上代碼,那么線程池將一直處于等待狀態(tài)而不能關(guān)閉牙寞,因?yàn)楹诵木€程不受keepAliveTime
控制饺鹃,所以會(huì)一直存活,程序也將一直不能結(jié)束间雀。運(yùn)行效果如下 (corePoolSize
設(shè)置為5悔详,其他參數(shù)不變)
控制臺(tái)輸出結(jié)果
# 線程打印開始
... ...
pool-1-thread-5
pool-1-thread-1
pool-1-thread-3
pool-1-thread-4
pool-1-thread-2
# 打印結(jié)束,但程序無(wú)法結(jié)束
2.2 Executors.newCachedThrteadPool() 創(chuàng)建線程池
Executors 是 JDK 自帶的線程池框架類惹挟,包含多個(gè)創(chuàng)建不同類型線程池的方法茄螃,而其中的newCachedThrteadPool()
方法也將核心線程數(shù)設(shè)置為了0并指定了線程存活時(shí)間,所以也可以自動(dòng)關(guān)閉连锯。其源碼如下:
public class Executors {
... ...
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
... ...
}
如果用這個(gè)線程池運(yùn)行上面的代碼归苍,程序也會(huì)自動(dòng)退出,效果如下
# 線程打印開始
... ...
pool-1-thread-7
pool-1-thread-5
pool-1-thread-4
pool-1-thread-1
pool-1-thread-9
# 打印結(jié)束运怖,程序等待60s后退出
Process finished with exit code 0
2. 線程池自動(dòng)關(guān)閉的情況二:通過 allowCoreThreadTimeOut 控制核心線程存活時(shí)間
通過將核心線程數(shù)設(shè)置為0雖然可以實(shí)現(xiàn)線程池的自動(dòng)關(guān)閉霜医,但也存在一些弊端,此話怎講驳规,先來(lái)看一下線程池的執(zhí)行流程:
如圖所示肴敛,當(dāng)有新的任務(wù)到來(lái)時(shí),程序會(huì)先判斷線程池當(dāng)前線程數(shù)是否達(dá)到corePoolSize
(核心線程數(shù))吗购,沒達(dá)到則創(chuàng)建線程執(zhí)行任務(wù)医男,達(dá)到則嘗試將任務(wù)放入任務(wù)隊(duì)列 (workQueue
)。如果將corePoolSize
設(shè)置為0的話捻勉,新到來(lái)的任務(wù)會(huì)永遠(yuǎn)優(yōu)先被放入任務(wù)隊(duì)列镀梭,然后等待被處理,這顯然會(huì)影響程序的執(zhí)行效率踱启。那你可能要問了报账,有沒有其他的方法來(lái)自己實(shí)現(xiàn)可自動(dòng)關(guān)閉的線程池呢研底?答案是肯定的,從 JDK 1.6 開始透罢,ThreadPoolExecutor 類新增了一個(gè)allowCoreThreadTimeOut
字段:
/**
* If false (default), core threads stay alive even when idle.
* If true, core threads use keepAliveTime to time out waiting
* for work.
* 默認(rèn)為false榜晦,核心線程處于空閑狀態(tài)也可一直存活
* 如果設(shè)置為true,核心線程的存活狀態(tài)將受keepAliveTime控制羽圃,超時(shí)將被銷毀
*/
private volatile boolean allowCoreThreadTimeOut;
這個(gè)字段值默認(rèn)為false
乾胶,可使用allowCoreThreadTimeOut()
方法對(duì)其進(jìn)行設(shè)置,如果設(shè)置為 true朽寞,那么核心線程數(shù)也將受keepAliveTime
控制识窿,此方法源碼如下:
public void allowCoreThreadTimeOut(boolean value) {
// 核心線程存活時(shí)間必須大于0,一旦開啟脑融,keepAliveTime 也必須大于0
if (value && keepAliveTime <= 0)
throw new IllegalArgumentException("Core threads must have nonzero keep alive times");
// 將 allowCoreThreadTimeOut 值設(shè)為傳入的參數(shù)值
if (value != allowCoreThreadTimeOut) {
allowCoreThreadTimeOut = value;
// 開啟后喻频,清理所有的超時(shí)空閑線程,包括核心線程
if (value)
interruptIdleWorkers();
}
}
既然如此肘迎,接下來(lái)我們就借助這個(gè)方法實(shí)現(xiàn)一個(gè)可自動(dòng)關(guān)閉且核心線程數(shù)不為0的線程池半抱,這里直接在第一個(gè)程序的基礎(chǔ)上進(jìn)行改進(jìn):
public class ThreadPoolTest {
public static void main(String[] args) {
// 這里把corePoolSize設(shè)為5,keepAliveTime保持不變
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 5,
30L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(15));
// 允許核心線程超時(shí)銷毀
executor.allowCoreThreadTimeOut(true);
for (int i = 0; i < 20; i++) {
executor.execute(() -> {
System.out.println(Thread.currentThread().getName());
});
}
}
}
運(yùn)行結(jié)果
# 線程打印開始
... ...
pool-1-thread-1
pool-1-thread-2
pool-1-thread-3
pool-1-thread-4
pool-1-thread-5
# 打印結(jié)束膜宋,程序等待30s后退出
Process finished with exit code 0
可以看到窿侈,程序在打印結(jié)束后等待了30s,然后自行退出秋茫,說(shuō)明線程池已自動(dòng)關(guān)閉史简,也就是allowCoreThreadTimeOut()
方法發(fā)揮了作用。這樣肛著,我們就實(shí)現(xiàn)了可自動(dòng)關(guān)閉且核心線程數(shù)不為0的線程池圆兵。
3. 超詳細(xì)的線程池執(zhí)行流程圖
讓我們?cè)賮?lái)梳理一下更完整的線程池執(zhí)行流程。
4. 結(jié)語(yǔ)
以上就是線程池可以自動(dòng)關(guān)閉的兩種情況枢贿,而且梳理了詳細(xì)的線程池執(zhí)行流程殉农,相信你看完本文一定會(huì)有所收獲。不過話又說(shuō)回來(lái)局荚,可自動(dòng)關(guān)閉的線程池的實(shí)際應(yīng)用場(chǎng)景并不多超凳,更多時(shí)候需要我們手動(dòng)關(guān)閉。下一篇文章我們就來(lái)聊聊如何手動(dòng)關(guān)閉線程池耀态,敬請(qǐng)期待轮傍!