線程池是一種池技術(shù)尺棋,就像連接池一樣封锉。線程池本身也是一個對象,這個對象可以管理自己池子中的眾多線程膘螟,以使他們被高效率的反復(fù)利用
-
為何要使用線程池
-
降低資源消耗成福,防止資源不足。
線程也是對象荆残,頻繁的創(chuàng)建線程對象奴艾,就會頻繁的去觸發(fā)內(nèi)存分配和內(nèi)存占用,大量消耗性能内斯,內(nèi)存占用過多可能會導(dǎo)致內(nèi)存溢出蕴潦。
頻繁的創(chuàng)建和銷毀線程對象即頻繁的消耗cpu像啼,影響服務(wù)器性能。
因為有大量的對象產(chǎn)生品擎,就會有大量的GC回收埋合,大量的gc可能會導(dǎo)致gc抖動,卡頓等現(xiàn)象萄传,也會影響服務(wù)器性能甚颂。 -
提高響應(yīng)速度
當(dāng)需要被線程執(zhí)行的任務(wù)到達時,因線程池中已有創(chuàng)建好的線程秀菱,所以可以直接用創(chuàng)建好的線程去執(zhí)行任務(wù)振诬,而無需先創(chuàng)建一個線程,然后在執(zhí)行任務(wù)衍菱,節(jié)省了程序運行時間赶么。 -
提高線程的可管理性
可以控制線程的數(shù)量和并法數(shù),也可以防止無限制的創(chuàng)建線程數(shù)量導(dǎo)致性能和內(nèi)存溢出等問題脊串。 -
提供更強大的功能辫呻,延時定時線程池
比如,我們需要一個任務(wù)每一秒鐘執(zhí)行一次琼锋。
-
java線程池種類
newCachedThreadPool創(chuàng)建一個可緩存線程池放闺,如果線程池長度超過處理需要,可靈活回收空閑線程缕坎,若無可回收怖侦,則新建線程。
newFixedThreadPool 創(chuàng)建一個定長線程池谜叹,可控制線程最大并發(fā)數(shù)匾寝,超出的線程會在隊列中等待。
newScheduledThreadPool 創(chuàng)建一個定長線程池荷腊,支持定時及周期性任務(wù)執(zhí)行艳悔。
newSingleThreadExecutor 創(chuàng)建一個單線程化的線程池,它只會用唯一的工作線程來執(zhí)行任務(wù)女仰,保證所有任務(wù)按照指定順序(FIFO, LIFO, 優(yōu)先級)執(zhí)行
不過在工作中很钓,如果你使用Executors的方式去創(chuàng)建線程,可能會失業(yè)的董栽,原因直接貼上阿里開發(fā)手冊的說明。
-
正確創(chuàng)建線程池的姿勢
-
線程池創(chuàng)建線程執(zhí)行和拒絕策略如下:
當(dāng)一個任務(wù)通過 execute(Runnable) 方法欲添加到線程池時企孩,線程池采用的策略如下(即添加任務(wù)的策略):
如果此時線程池中的數(shù)量小于 corePoolSize 锭碳,即使線程池中的線程都處于空閑狀態(tài),也要創(chuàng)建新的線程來處理被添加的任務(wù)勿璃。
如果此時線程池中的數(shù)量等于 corePoolSize 擒抛,但是緩沖隊列 workQueue 未滿推汽,那么任務(wù)被放入緩沖隊列。
如果此時線程池中的數(shù)量大于 corePoolSize 歧沪,緩沖隊列 workQueue 滿歹撒,并且線程池中的數(shù)量小于maximumPoolSize ,建新的線程來處理被添加的任務(wù)诊胞。
如果此時線程池中的數(shù)量大于 corePoolSize 暖夭,緩沖隊列 workQueue 滿,并且線程池中的數(shù)量等于maximumPoolSize 撵孤,那么通過 handler 所指定的策略來處理此任務(wù)迈着。
-
使用有界隊列時,如果任務(wù)隊列滿了則執(zhí)行拒絕策略(第二個用的多些)
ThreadPoolExecutor.AbortPolicy 丟棄任務(wù)邪码,并拋出 RejectedExecutionException 異常裕菠。備注:此種方法必須要捕獲線程池添加任務(wù)代碼,不然添加異常就不會走shutdown關(guān)閉線程池代碼闭专,會導(dǎo)致線程池一直處于未關(guān)閉狀態(tài)奴潘。
ThreadPoolExecutor.CallerRunsPolicy:該任務(wù)被線程池拒絕,由調(diào)用 execute方法的線程執(zhí)行該任務(wù)影钉。備注:其實就是主線程去執(zhí)行被線程池拒絕的任務(wù)画髓。
ThreadPoolExecutor.DiscardOldestPolicy : 拋棄隊列最前面的任務(wù),然后重新嘗試執(zhí)行任務(wù)斧拍。備注:就是移除隊列中第一個添加進去的任務(wù)雀扶,把后來的任務(wù)加在隊列最后。
ThreadPoolExecutor.DiscardPolicy肆汹,丟棄任務(wù)愚墓,不過也不拋出異常。備注:直接丟棄隊列滿了之后添加進來的任務(wù)昂勉。
-
如何優(yōu)雅的關(guān)閉線程池:
線程池有2種關(guān)閉方法:
- shutdown():線程池拒接收新提交的任務(wù)浪册,同時等待線程池里的任務(wù)執(zhí)行完畢后關(guān)閉線程池。
- shutdownNow() :線程池拒接收新提交的任務(wù)岗照,同時立馬關(guān)閉線程池村象,線程池里的任務(wù)不再執(zhí)行,并能獲取未被執(zhí)行的任務(wù)攒至。
關(guān)閉線程池的時候厚者,線程池中的線程有4種狀態(tài):
- 處于空閑狀態(tài) — shutdown: 正常退出,shutdownNow: 正常退出
- 處于正在處理任務(wù)的狀態(tài) — shutdown: 任務(wù)處理完成正常退出迫吐,shutdownNow: 任務(wù)處理完成正常退出
- 處于正在從任務(wù)隊列讀取任務(wù)的狀態(tài)— shutdown: 繼續(xù)讀取并任務(wù)執(zhí)行库菲,直到所有任務(wù)全部執(zhí)行完畢退出,shutdownNow: 停止讀取并退出
- 處于阻塞狀態(tài)— shutdown: 線程繼續(xù)阻塞志膀,并等待阻塞結(jié)束繼續(xù)執(zhí)行任務(wù)熙宇,shutdownNow: 拋出InterruptedException異常
所以鳖擒,我們該如何正確關(guān)閉線程池
- 當(dāng)調(diào)用shutdownNow()方法時,如果確定可能會有阻塞的任務(wù)存在烫止,一定要捕獲異常進行處理
- 當(dāng)調(diào)用shutdown()方法時蒋荚,一定要確保任務(wù)里不會有永久阻塞等待的線程,否則線程池就關(guān)閉不了馆蠕,不行的話可以等待一段時間后調(diào)用shutdownNow()方法
如果要在線程池任務(wù)執(zhí)行完關(guān)閉之后才能執(zhí)行其他的主邏輯期升,那么我們就必須要等到線程池任務(wù)全部執(zhí)行結(jié)束,需要注意的是荆几,不管是shutdown還是shutdownNow方法吓妆,其實都是發(fā)起線程關(guān)閉,但是線程池此時并不一定完全關(guān)閉了吨铸,因為可能有線程還在執(zhí)行任務(wù)或者隊列里還有任務(wù)等待執(zhí)行行拢,所以我們需要通過pool.awaitTermination(2, TimeUnit.SECONDS)方法去判斷線程池是否真的完全關(guān)閉了
- pool.awaitTermination(2, TimeUnit.SECONDS)
第一個參數(shù)為時間,第二個參數(shù)為單位
這是一個阻塞方法诞吱,返回true和false舟奠,線程池關(guān)閉為true,未關(guān)閉為false
在指定時間內(nèi)房维,如果線程池關(guān)閉了沼瘫,此方法結(jié)束阻塞,返回true咙俩,繼續(xù)執(zhí)行之后的代碼
在指定時間內(nèi)耿戚,如果線程池未關(guān)閉,會一直阻塞阿趁,直到指定時間到了返回false膜蛔。
注意:此方法為阻塞方法,如果任務(wù)沒有結(jié)束脖阵,子線程會阻塞于此皂股,主線程也會被阻塞等待,當(dāng)需要線程池的任務(wù)全部結(jié)束才能執(zhí)行主線程時命黔,可以用此方法呜呐,輸入一個時間比較長的參數(shù)
所以,一般shutdown()或者shutdownNow()方法要配合awaitTermination()方法一起使用悍募。
其實關(guān)鍵就在于是否有阻塞的任務(wù)蘑辑,下面是一種參考處理方法
threadPool.shutdown(); // Disable new tasks from being submitted
// 設(shè)定最大重試次數(shù)
try {
// 等待 60 s
if (!threadPool.awaitTermination(60, TimeUnit.SECONDS)) {
// 調(diào)用 shutdownNow 取消正在執(zhí)行的任務(wù)
threadPool.shutdownNow();
// 再次等待 60 s,如果還未結(jié)束坠宴,可以再次嘗試以躯,或者直接放棄
if (!threadPool.awaitTermination(60, TimeUnit.SECONDS))
System.err.println("線程池任務(wù)未正常執(zhí)行結(jié)束");
}
} catch (InterruptedException ie) {
// 重新調(diào)用 shutdownNow
threadPool.shutdownNow();
}
線程關(guān)閉的參考資料:
https://www.cnblogs.com/qingquanzi/p/9018627.html
https://cloud.tencent.com/developer/article/1523115
https://blog.csdn.net/chun_hua_xue_yue/article/details/96475075