1、什么是線程池
線程池的基本思想是一種對(duì)象池损趋,在程序啟動(dòng)時(shí)就開(kāi)辟一塊內(nèi)存空間患久,里面存放了眾多(未死亡)的線程,池中線程執(zhí)行調(diào)度由池管理器來(lái)處理。當(dāng)有線程任務(wù)時(shí)蒋失,從池中取一個(gè)返帕,執(zhí)行完成后線程對(duì)象歸池,這樣可以避免反復(fù)創(chuàng)建線程對(duì)象所帶來(lái)的性能開(kāi)銷高镐,節(jié)省了系統(tǒng)的資源溉旋。
記得點(diǎn)贊收藏加關(guān)注哦 畸冲,我這里也準(zhǔn)備了很多面試熱門知識(shí)點(diǎn)和大廠面試題嫉髓,希望對(duì)大家有幫助!有需要的朋友
可以加q群:580763979? ? ??備注:簡(jiǎn)書? ?免費(fèi)領(lǐng)取~
減少了創(chuàng)建和銷毀線程的次數(shù)算行,每個(gè)工作線程都可以被重復(fù)利用,可執(zhí)行多個(gè)任務(wù)苫耸。
運(yùn)用線程池能有效的控制線程最大并發(fā)數(shù)州邢,可以根據(jù)系統(tǒng)的承受能力,調(diào)整線程池中工作線線程的數(shù)目褪子,防止因?yàn)橄倪^(guò)多的內(nèi)存量淌,而把服務(wù)器累趴下(每個(gè)線程需要大約1MB內(nèi)存,線程開(kāi)的越多嫌褪,消耗的內(nèi)存也就越大呀枢,最后死機(jī))。
對(duì)線程進(jìn)行一些簡(jiǎn)單的管理笼痛,比如:延時(shí)執(zhí)行裙秋、定時(shí)循環(huán)執(zhí)行的策略等,運(yùn)用線程池都能進(jìn)行很好的實(shí)現(xiàn)
一個(gè)線程池包括以下四個(gè)基本組成部分:
線程池管理器(ThreadPool):用于創(chuàng)建并管理線程池摘刑,包括 創(chuàng)建線程池,銷毀線程池刻坊,添加新任務(wù)枷恕;
工作線程(WorkThread):線程池中線程,在沒(méi)有任務(wù)時(shí)處于等待狀態(tài)谭胚,可以循環(huán)的執(zhí)行任務(wù)活尊;
任務(wù)接口(Task):每個(gè)任務(wù)必須實(shí)現(xiàn)的接口,以供工作線程調(diào)度任務(wù)的執(zhí)行漏益,它主要規(guī)定了任務(wù)的入口蛹锰,任務(wù)執(zhí)行完后的收尾工作,任務(wù)的執(zhí)行狀態(tài)等绰疤;
任務(wù)隊(duì)列(taskQueue):用于存放沒(méi)有處理的任務(wù)铜犬。提供一種緩沖機(jī)制。
參數(shù)的設(shè)置跟系統(tǒng)的負(fù)載有直接的關(guān)系癣猾,下面為系統(tǒng)負(fù)載的相關(guān)參數(shù):
tasks敛劝,每秒需要處理的的任務(wù)數(shù)(針對(duì)系統(tǒng)需求)
threadtasks,每個(gè)線程每鈔可處理任務(wù)數(shù)(針對(duì)線程本身)
responsetime纷宇,系統(tǒng)允許任務(wù)最大的響應(yīng)時(shí)間夸盟,比如每個(gè)任務(wù)的響應(yīng)時(shí)間不得超過(guò)2秒。
系統(tǒng)每秒有tasks個(gè)任務(wù)需要處理理像捶,則每個(gè)線程每鈔可處理threadtasks個(gè)任務(wù)上陕。,則需要的線程數(shù)為:tasks/threadtasks拓春,即tasks/threadtasks個(gè)線程數(shù)释簿。
假設(shè)系統(tǒng)每秒任務(wù)數(shù)為100 ~ 1000,每個(gè)線程每鈔可處理10個(gè)任務(wù)硼莽,則需要100 / 10至1000 / 10庶溶,即10 ~ 100個(gè)線程。那么corePoolSize應(yīng)該設(shè)置為大于10懂鸵,具體數(shù)字最好根據(jù)8020原則偏螺,因?yàn)橄到y(tǒng)每秒任務(wù)數(shù)為100 ~ 1000,即80%情況下系統(tǒng)每秒任務(wù)數(shù)小于1000 * 20% = 200匆光,則corePoolSize可設(shè)置為200 / 10 = 20套像。
任務(wù)隊(duì)列的長(zhǎng)度要根據(jù)核心線程數(shù),以及系統(tǒng)對(duì)任務(wù)響應(yīng)時(shí)間的要求有關(guān)殴穴。隊(duì)列長(zhǎng)度可以設(shè)置為 所有核心線程每秒處理任務(wù)數(shù) * 每個(gè)任務(wù)響應(yīng)時(shí)間 = 每秒任務(wù)總響應(yīng)時(shí)間 凉夯,即(corePoolSize*threadtasks)responsetime: (2010)*2=400,即隊(duì)列長(zhǎng)度可設(shè)置為400采幌。
當(dāng)系統(tǒng)負(fù)載達(dá)到最大值時(shí)劲够,核心線程數(shù)已無(wú)法按時(shí)處理完所有任務(wù),這時(shí)就需要增加線程休傍。每秒200個(gè)任務(wù)需要20個(gè)線程征绎,那么當(dāng)每秒達(dá)到1000個(gè)任務(wù)時(shí),則需要(tasks - queueCapacity)/ threadtasks 即(1000-400)/10磨取,即60個(gè)線程人柿,可將maxPoolSize設(shè)置為60。
隊(duì)列長(zhǎng)度設(shè)置過(guò)大忙厌,會(huì)導(dǎo)致任務(wù)響應(yīng)時(shí)間過(guò)長(zhǎng)凫岖,切忌以下寫法:
LinkedBlockingQueue queue = new LinkedBlockingQueue();
這實(shí)際上是將隊(duì)列長(zhǎng)度設(shè)置為Integer.MAX_VALUE,將會(huì)導(dǎo)致線程數(shù)量永遠(yuǎn)為corePoolSize逢净,再也不會(huì)增加哥放,當(dāng)任務(wù)數(shù)量陡增時(shí)歼指,任務(wù)響應(yīng)時(shí)間也將隨之陡增。
當(dāng)負(fù)載降低時(shí)甥雕,可減少線程數(shù)量踩身,當(dāng)線程的空閑時(shí)間超過(guò)keepAliveTime,會(huì)自動(dòng)釋放線程資源社露。默認(rèn)情況下線程池停止多余的線程并最少會(huì)保持corePoolSize個(gè)線程挟阻。
默認(rèn)情況下核心線程不會(huì)退出,可通過(guò)將該參數(shù)設(shè)置為true峭弟,讓核心線程也退出附鸽。
一般說(shuō)來(lái),大家認(rèn)為線程池的大小經(jīng)驗(yàn)值應(yīng)該這樣設(shè)置:(其中N為CPU的個(gè)數(shù))
如果是CPU密集型應(yīng)用孟害,則線程池大小設(shè)置為N+1
如果是IO密集型應(yīng)用拒炎,則線程池大小設(shè)置為2N+1
線程池的初始化狀態(tài)是RUNNING挨务,能夠接收新任務(wù),以及對(duì)已添加的任務(wù)進(jìn)行處理玉组。
線程池處在SHUTDOWN狀態(tài)時(shí)谎柄,不接收新任務(wù),但能處理已添加的任務(wù)惯雳。 調(diào)用線程池的shutdown()接口時(shí)朝巫,線程池由RUNNING -> SHUTDOWN。
線程池處在STOP狀態(tài)時(shí)石景,不接收新任務(wù)劈猿,不處理已添加的任務(wù),并且會(huì)中斷正在處理的任務(wù)潮孽。 調(diào)用線程池的shutdownNow()接口時(shí)揪荣,線程池由(RUNNING or SHUTDOWN ) -> STOP。
當(dāng)所有的任務(wù)已終止往史,ctl記錄的”任務(wù)數(shù)量”為0仗颈,線程池會(huì)變?yōu)門IDYING狀態(tài)。當(dāng)線程池變?yōu)門IDYING狀態(tài)時(shí)椎例,會(huì)執(zhí)行鉤子函數(shù)terminated()挨决。terminated()在ThreadPoolExecutor類中是空的,若用戶想在線程池變?yōu)門IDYING時(shí)订歪,進(jìn)行相應(yīng)的處理脖祈;可以通過(guò)重載terminated()函數(shù)來(lái)實(shí)現(xiàn)。
當(dāng)線程池在SHUTDOWN狀態(tài)下刷晋,阻塞隊(duì)列為空并且線程池中執(zhí)行的任務(wù)也為空時(shí)盖高,就會(huì)由 SHUTDOWN -> TIDYING福压。
當(dāng)線程池在STOP狀態(tài)下,線程池中執(zhí)行的任務(wù)為空時(shí)或舞,就會(huì)由STOP -> TIDYING荆姆。 線程池徹底終止,就變成TERMINATED狀態(tài)映凳。線程池處在TIDYING狀態(tài)時(shí)胆筒,執(zhí)行完terminated()之后,就會(huì)由 TIDYING -> TERMINATED诈豌。
線程池提供兩種關(guān)閉線程池方法:shutDown()和shutdownNow()
當(dāng)線程池調(diào)用該方法時(shí),線程池的狀態(tài)則立刻變成SHUTDOWN狀態(tài)。此時(shí)矫渔,則不能再往線程池中添加任何任務(wù)彤蔽,否則將會(huì)拋出RejectedExecutionException異常。但是庙洼,此時(shí)線程池不會(huì)立刻退出顿痪,直到添加到線程池中的任務(wù)都已經(jīng)處理完成,才會(huì)退出油够。
根據(jù)JDK文檔描述蚁袭,大致意思是:執(zhí)行該方法,線程池的狀態(tài)立刻變成STOP狀態(tài)石咬,并試圖停止所有正在執(zhí)行的線程揩悄,不再處理還在池隊(duì)列中等待的任務(wù),當(dāng)然鬼悠,它會(huì)返回那些未執(zhí)行的任務(wù)删性。
它試圖終止線程的方法是通過(guò)調(diào)用Thread.interrupt()方法來(lái)實(shí)現(xiàn)的,但是大家知道焕窝,這種方法的作用有限蹬挺,如果線程中沒(méi)有sleep、wait袜啃、Condition汗侵、定時(shí)鎖等應(yīng)用, interrupt()方法是無(wú)法中斷當(dāng)前的線程的。所以群发,ShutdownNow()并不代表線程池就一定立即就能退出晰韵,它可能必須要等待所有正在執(zhí)行的任務(wù)都執(zhí)行完成了才能退出。
7熟妓、各種場(chǎng)景下怎么設(shè)置線程數(shù)
高并發(fā)雪猪、任務(wù)執(zhí)行時(shí)間短的業(yè)務(wù)怎樣使用線程池?
線程池線程數(shù)可以設(shè)置為CPU核數(shù)+1起愈,減少線程上下文的切換
并發(fā)不高只恨、任務(wù)執(zhí)行時(shí)間長(zhǎng)的業(yè)務(wù)怎樣使用線程池译仗?
這個(gè)需要判斷執(zhí)行時(shí)間是耗在哪個(gè)地方
假如是業(yè)務(wù)時(shí)間長(zhǎng)集中在IO操作上,也就是IO密集型的任務(wù)官觅,因?yàn)镮O操作并不占用CPU纵菌,所以不要讓所有的CPU閑下來(lái),可以適當(dāng)加大線程池中的線程數(shù)目(2 * CPU核數(shù))休涤,讓CPU處理更多的業(yè)務(wù)咱圆。
假如是業(yè)務(wù)時(shí)間長(zhǎng)集中在計(jì)算操作上,也就是CPU密集型任務(wù)功氨,和(1)CPU核數(shù)+1 一樣吧序苏,線程池中的線程數(shù)設(shè)置得少一些,減少線程上下文的切換
并發(fā)高捷凄、業(yè)務(wù)執(zhí)行時(shí)間長(zhǎng)的業(yè)務(wù)怎樣使用線程池忱详?
解決這種類型任務(wù)的關(guān)鍵不在于線程池而在于整體架構(gòu)的設(shè)計(jì)
這樣的處理方式更加明確線程池的運(yùn)行規(guī)則匈睁,規(guī)避資源耗盡的風(fēng)險(xiǎn)
newFixedThreadPool和newSingleThreadExecutor
上面兩個(gè)主要問(wèn)題是堆積的請(qǐng)求處理隊(duì)列可能會(huì)耗費(fèi)非常大的內(nèi)存,甚至OOM
newCachedThreadPool和newScheduledThreadPool
上面兩個(gè)主要問(wèn)題是最大線程數(shù)是Integer.MAX_VALUE钦铁,可能會(huì)創(chuàng)建數(shù)量非常多的線程软舌,甚至OOM才漆。
通過(guò)阻塞隊(duì)列poll()黎比,讓線程阻塞等待一段時(shí)間,如果沒(méi)有取到任務(wù)鸳玩,則線程死亡
線程池為什么能維持線程不釋放阅虫,隨時(shí)運(yùn)行各種任務(wù)?
for (;;) {
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? Runnable r = timed ?
? ? ? ? ? ? ? ? ? ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
? ? ? ? ? ? ? ? ? ? workQueue.take();
? ? ? ? ? ? ? ? if (r != null)
? ? ? ? ? ? ? ? ? ? return r;
? ? ? ? ? ? ? ? timedOut = true;
? ? ? ? ? ? } catch (InterruptedException retry) {
? ? ? ? ? ? ? ? timedOut = false;
? ? ? ? ? ? }
? ? ? ? }
? ? }
在死循環(huán)中工作隊(duì)列workQueue會(huì)一直去拿任務(wù):
核心線程的會(huì)一直卡在 workQueue.take()方法不跟,讓線程一直等待颓帝,直到獲取到任務(wù),然后返回窝革。
非核心線程會(huì) workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) 购城,如果超時(shí)還沒(méi)有拿到,下一次循環(huán)判斷compareAndDecrementWorkerCount就會(huì)返回null,Worker對(duì)象的run()方法循環(huán)體的判斷為null,任務(wù)結(jié)束虐译,然后線程被系統(tǒng)回收瘪板。
通過(guò)阻塞隊(duì)列take(),讓線程一直等待漆诽,直到獲取到任務(wù)
將allowCoreThreadTimeOut設(shè)置為true锣枝。可用下面代碼實(shí)驗(yàn)
{
? ? // 允許釋放核心線程兰英,等待時(shí)間為100毫秒
? ? es.allowCoreThreadTimeOut(true);
? ? for(......){
? ? ? ? // 向線程池里添加任務(wù)撇叁,任務(wù)內(nèi)容為打印當(dāng)前線程池線程數(shù)
? ? ? ? Thread.currentThread().sleep(200);
? ? }
}
線程數(shù)會(huì)一直為1。 如果allowCoreThreadTimeOut為false畦贸,線程數(shù)會(huì)逐漸達(dá)到飽和税朴,然后大家一起阻塞等待。
線程池不區(qū)分核心線程于非核心線程正林,只是根據(jù)當(dāng)前線程池容量狀態(tài)做不同的處理來(lái)進(jìn)行調(diào)整,因此看起來(lái)像是有核心線程于非核心線程颤殴,實(shí)際上是滿足線程池期望達(dá)到的并發(fā)狀態(tài)觅廓。
線程執(zhí)行Worker涵但,Worker不斷從阻塞隊(duì)列里獲取任務(wù)來(lái)執(zhí)行杈绸。。
我這里準(zhǔn)備了一線大廠面試資料和超多超硬核的PDF技術(shù)文檔矮瘟,以及我為大家精心準(zhǔn)備的多套簡(jiǎn)歷模板(不斷更新中)瞳脓,希望大家都能找到心儀的工作!
有需要的朋友可以加q群:580763979? ? ??備注:簡(jiǎn)書? ?免費(fèi)領(lǐng)取~