java中線程池的關(guān)閉問(wèn)題
java線程池的關(guān)閉問(wèn)題
線程池是一個(gè)重要的資源,關(guān)閉線程池有兩種方式:手動(dòng)和自動(dòng)。接下來(lái)我們一一講解。
手動(dòng)關(guān)閉
線程池有兩個(gè)方法 shutdown()/shutdownNow()用來(lái)關(guān)閉,二者的區(qū)別:
shutdown()
執(zhí)行后停止接受新任務(wù),會(huì)把隊(duì)列的任務(wù)執(zhí)行完畢。shutdownNow()
也是停止接受新任務(wù),但會(huì)中斷所有的任務(wù),將線程池狀態(tài)變?yōu)?stop。
兩個(gè)方法都會(huì)中斷線程,用戶可自行判斷是否需要響應(yīng)中斷。
通常,我們可以設(shè)置一個(gè)shutdownHook來(lái)關(guān)閉線程池:
Runtime.getRuntime().addShutdownHook(new Thread(() -> { try { executorService.shutdown(); while(!executorService.awaitTermination(10,TimeUnit.SECONDS)) {} } catch(Exception e) { } }));
自動(dòng)關(guān)閉
首先要了解線程池在什么情況下會(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.
沒(méi)有引用指向且沒(méi)有剩余線程的線程池將會(huì)自動(dòng)關(guān)閉。
那么什么情況下線程池中會(huì)沒(méi)有剩余線程呢?
先來(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。
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)閉了嗎?
沒(méi)錯(cuò),這就是線程池自動(dòng)關(guān)閉的第一種情況。
1、自動(dòng)關(guān)閉線程池
核心線程數(shù)為 0 并指定線程存活時(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()); }); }
執(zhí)行上述代碼,當(dāng)線程打印結(jié)束后等待30s,程序退出(代表了線程池自動(dòng)關(guān)閉了)。
其實(shí)Executors.newCachedThrteadPool()創(chuàng)建的線程池,coreP00lSize=0且keepAliveTime=60s,所以也可以自動(dòng)關(guān)閉。
其源碼如下:
public class Executors { ... ... public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); } ... ... }
如果將上面示例的代碼corePoolSize改成大于0的數(shù),當(dāng)線程打印結(jié)束后程序一直不會(huì)退出。
2、自動(dòng)關(guān)閉線程池
通過(guò) allowCoreThreadTimeOut 控制核心線程存活時(shí)間
將核心線程數(shù)設(shè)置為0雖然可以實(shí)現(xiàn)線程池的自動(dòng)關(guān)閉,但也存在一些弊端,根據(jù)線程池工作原理,當(dāng)corePoolSize=0時(shí)新到來(lái)的任務(wù)會(huì)永遠(yuǎn)優(yōu)先被放入任務(wù)隊(duì)列,然后等待被處理,這顯然會(huì)影響程序的執(zhí)行效率。
那你可能要問(wèn)了,有沒(méi)有方法來(lái)關(guān)閉核心線程呢?
答案是肯定的,從 JDK 1.6 開(kāi)始,ThreadPoolExecutor 類新增了一個(gè)allowCoreThreadTimeOut字段,這個(gè)字段值默認(rèn)為false,可使用allowCoreThreadTimeOut()方法對(duì)其進(jìn)行設(shè)置,如果設(shè)置為 true,那么核心線程數(shù)也將受keepAliveTime控制,此方法源碼如下:
public void allowCoreThreadTimeOut(boolean value) { // 核心線程存活時(shí)間必須大于0,一旦開(kāi)啟,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; // 開(kāi)啟后,清理所有的超時(shí)空閑線程,包括核心線程 if (value) interruptIdleWorkers(); } }
接下來(lái),把上面例子修改一下,運(yùn)行:
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 5, 30L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(15)); executor.allowCoreThreadTimeOut(true); for (int i = 0; i < 20; i++) { executor.execute(() -> { // 簡(jiǎn)單地打印當(dāng)前線程名稱 System.out.println(Thread.currentThread().getName()); }); }
雖然corePoolSize>0,通過(guò)設(shè)置allowCoreThreadTimeOut=true,當(dāng)線程打印結(jié)束后等待30s,程序正常退出(說(shuō)明線程池自動(dòng)關(guān)閉了)
3、自動(dòng)關(guān)閉線程池
線程池中的線程設(shè)置為守護(hù)線程
ThreadPoolExecutor executor2 = new ThreadPoolExecutor(1, 5, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(150), new ThreadFactory() { public Thread newThread(Runnable r) { Thread thread = new Thread(r, r.hashCode()+""); thread.setDaemon(true);//設(shè)置成守護(hù)線程 return thread; } } ); for (int i = 0; i < 20; i++) { executor2.execute(() -> { // 簡(jiǎn)單地打印當(dāng)前線程名稱 System.out.println(Thread.currentThread().getName()); }); }
雖然corePoolSize>0,而且沒(méi)有設(shè)置allowCoreThreadTimeOut,但是在創(chuàng)建線程池時(shí)通過(guò)ThreadFactory指定了線程為守護(hù)線程。
當(dāng)線程打印結(jié)束后,無(wú)需等待程序正常退出(說(shuō)明線程池自動(dòng)關(guān)閉了)。
當(dāng)然,這里是在main函數(shù)中執(zhí)行,不存在其他非守護(hù)線程哈。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringMVC異常全局捕獲與錯(cuò)誤響應(yīng)的處理方法
編程式異常處理是通過(guò)在代碼中?顯式編寫異常捕獲邏輯(如?try-catch?塊)來(lái)管理異常的方式,開(kāi)發(fā)者需要手動(dòng)處理每一個(gè)可能拋出異常的代碼段,本文給大家介紹SpringMVC異常全局捕獲與錯(cuò)誤響應(yīng)的處理方法,感興趣的朋友一起看看吧2025-03-03Java計(jì)時(shí)新姿勢(shì)StopWatch詳解
這篇文章主要介紹了Java計(jì)時(shí)新姿勢(shì)StopWatch,最近公司來(lái)了個(gè)大佬,從他那里學(xué)到不少東西,其中一個(gè)就是計(jì)時(shí)的新姿勢(shì)「StopWatch」,需要的朋友可以參考下2019-07-07Springboot?引入?Redis?并配置序列化并封裝RedisTemplate?
這篇文章主要介紹了Springboot?引入?Redis?并配置序列化并封裝RedisTemplate。文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-09-09Java遠(yuǎn)程連接Linux服務(wù)器并執(zhí)行命令及上傳文件功能
這篇文章主要介紹了Java遠(yuǎn)程連接Linux服務(wù)器并執(zhí)行命令及上傳文件功能,本文是小編整理的代碼筆記,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-05-05SpringDataJPA之Specification復(fù)雜查詢實(shí)戰(zhàn)
這篇文章主要介紹了SpringDataJPA之Specification復(fù)雜查詢實(shí)戰(zhàn),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11Javafx利用fxml變換場(chǎng)景的實(shí)現(xiàn)示例
本文主要介紹了Javafx利用fxml變換場(chǎng)景的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-07-07