線程池的可創(chuàng)建種類
使用Executors.newCachedThreadPool可以快速創(chuàng)建一個(gè)擁有自動(dòng)回收線程功能且沒(méi)有限制的線程池。
使用Executors.newFixedThreadPool可以用來(lái)創(chuàng)建一個(gè)固定線程大小的線程池。
使用Executors.newSingleThreadExecutor可以用來(lái)創(chuàng)建一個(gè)單線程的執(zhí)行器。
通過(guò)參數(shù)corePoolSize和maximumPoolSize來(lái)配置,實(shí)例化一個(gè)實(shí)例
線程的創(chuàng)建
當(dāng)任務(wù)通過(guò)executor提交給線程池的時(shí)候,我們需要知道下面幾個(gè)點(diǎn):
如果這個(gè)時(shí)候當(dāng)前池子中的工作線程數(shù)小于corePoolSize,則新創(chuàng)建一個(gè)新的工作線程來(lái)執(zhí)行這個(gè)任務(wù),不管工作線程集合中有沒(méi)有線程是處于空閑狀態(tài)。
如果池子中有比corePoolSize大的但是比maximumPoolSize小的工作線程,任務(wù)會(huì)首先被嘗試著放入隊(duì)列,這里有兩種情況需要單獨(dú)說(shuō)一下:
a. 如果任務(wù)被成功的放入隊(duì)列,則看看是否需要開(kāi)啟新的線程來(lái)執(zhí)行任務(wù),只有當(dāng)當(dāng)前工作線程數(shù)為0的時(shí)候才會(huì)創(chuàng)建新的線程,因?yàn)橹暗木€程有可能因?yàn)槎继幱诳臻e狀態(tài)或因?yàn)楣ぷ鹘Y(jié)束而被移除。
b. 如果放入隊(duì)列失敗,則才會(huì)去創(chuàng)建新的工作線程。
如果corePoolSize和maximumPoolSize相同,則線程池的大小是固定的。
通過(guò)將maximumPoolSize設(shè)置為無(wú)限大,我們可以得到一個(gè)無(wú)上限的線程池。
除了通過(guò)構(gòu)造參數(shù)設(shè)置這幾個(gè)線程池參數(shù)之外我們還可以在運(yùn)行時(shí)設(shè)置。
默認(rèn)情況下,核心工作線程值在初始的時(shí)候被創(chuàng)建,當(dāng)新任務(wù)來(lái)到的時(shí)候被啟動(dòng),但是我們可以通過(guò)重寫(xiě)prestartCoreThread或prestartCoreThreads方法來(lái)改變這種行為。通常場(chǎng)景我們可以在應(yīng)用啟動(dòng)的時(shí)候來(lái)WarmUp核心線程,從而達(dá)到任務(wù)過(guò)來(lái)能夠立馬執(zhí)行的結(jié)果,使得初始任務(wù)處理的時(shí)間得到一定優(yōu)化。
線程的回收
如果當(dāng)前池子中的工作線程數(shù)大于corePoolSize,如果超過(guò)這個(gè)數(shù)字的線程處于空閑的時(shí)間大于keepAliveTime,則這些線程將會(huì)被終止,這是一種減少不必要資源消耗的策略。這個(gè)參數(shù)可以在運(yùn)行時(shí)被改變,我們同樣可以將這種策略應(yīng)用給核心線程,我們可以通過(guò)調(diào)用allowCoreThreadTimeout來(lái)實(shí)現(xiàn)。
選擇合適的阻塞隊(duì)列
所有的阻塞隊(duì)列都可以被用來(lái)存放任務(wù),但是使用不同的隊(duì)列針對(duì)corePoolSize會(huì)表現(xiàn)不同的行為:
當(dāng)池中工作線程數(shù)小于corePoolSize的時(shí)候,每次來(lái)任務(wù)的時(shí)候都會(huì)創(chuàng)建一個(gè)新的工作線程。
當(dāng)池中工作線程數(shù)大于等于corePoolSize的時(shí)候,每次任務(wù)來(lái)的時(shí)候都會(huì)首先嘗試將線程放入隊(duì)列,而不是直接去創(chuàng)建線程。
如果放入隊(duì)列失敗,且當(dāng)先池中線程數(shù)小于maximumPoolSize的時(shí)候,則會(huì)創(chuàng)建一個(gè)工作線程。
下面主要是不同隊(duì)列策略表現(xiàn):
直接遞交:一種比較好的默認(rèn)選擇是使用SynchronousQueue,這種策略會(huì)將提交的任務(wù)直接傳送給工作線程,而不持有。如果當(dāng)前沒(méi)有工作線程來(lái)處理,即任務(wù)放入隊(duì)列失敗,則根據(jù)線程池的實(shí)現(xiàn),會(huì)引發(fā)新的工作線程創(chuàng)建,因此新提交的任務(wù)會(huì)被處理。這種策略在當(dāng)提交的一批任務(wù)之間有依賴關(guān)系的時(shí)候避免了鎖競(jìng)爭(zhēng)消耗。值得一提的是,這種策略最好是配合unbounded線程數(shù)來(lái)使用,從而避免任務(wù)被拒絕。同時(shí)我們必須要考慮到一種場(chǎng)景,當(dāng)任務(wù)到來(lái)的速度大于任務(wù)處理的速度,將會(huì)引起無(wú)限制的線程數(shù)不斷的增加。
無(wú)界隊(duì)列:使用無(wú)界隊(duì)列如LinkedBlockingQueue沒(méi)有指定最大容量的時(shí)候,將會(huì)引起當(dāng)核心線程都在忙的時(shí)候,新的任務(wù)被放在隊(duì)列上,因此,永遠(yuǎn)不會(huì)有大于corePoolSize的線程被創(chuàng)建,因此maximumPoolSize參數(shù)將失效。這種策略比較適合所有的任務(wù)都不相互依賴,獨(dú)立執(zhí)行。舉個(gè)例子,如網(wǎng)頁(yè)服務(wù)器中,每個(gè)線程獨(dú)立處理請(qǐng)求。但是當(dāng)任務(wù)處理速度小于任務(wù)進(jìn)入速度的時(shí)候會(huì)引起隊(duì)列的無(wú)限膨脹。
有界隊(duì)列:有界隊(duì)列如ArrayBlockingQueue幫助限制資源的消耗,但是不容易控制。隊(duì)列長(zhǎng)度和maximumPoolSize這兩個(gè)值會(huì)相互影響,使用大的隊(duì)列和小maximumPoolSize會(huì)減少CPU的使用、操作系統(tǒng)資源、上下文切換的消耗,但是會(huì)降低吞吐量,如果任務(wù)被頻繁的阻塞如IO線程,系統(tǒng)其實(shí)可以調(diào)度更多的線程。使用小的隊(duì)列通常需要大maximumPoolSize,從而使得CPU更忙一些,但是又會(huì)增加降低吞吐量的線程調(diào)度的消耗。總結(jié)一下是IO密集型可以考慮多些線程來(lái)平衡CPU的使用,CPU密集型可以考慮少些線程減少線程調(diào)度的消耗。
選擇適合的拒絕策略
當(dāng)新的任務(wù)到來(lái)的而線程池被關(guān)閉的時(shí)候,或線程數(shù)和隊(duì)列已經(jīng)達(dá)到上限的時(shí)候,我們需要去做一個(gè)決定,怎么拒絕這些任務(wù)。下面介紹一下常用的策略:
- ThreadPoolExecutor#AbortPolicy:這個(gè)策略直接拋出RejectedExecutionException異常。
- ThreadPoolExecutor#CallerRunsPolicy:這個(gè)策略將會(huì)使用Caller線程來(lái)執(zhí)行這個(gè)任務(wù),這是一種feedback策略,可以降低任務(wù)提交的速度。
- ThreadPoolExecutor#DiscardPolicy:這個(gè)策略將會(huì)直接丟棄任務(wù)。
- ThreadPoolExecutor#DiscardOldestPolicy:這個(gè)策略將會(huì)把任務(wù)隊(duì)列頭部的任務(wù)丟棄,然后重新嘗試執(zhí)行,如果還是失敗則繼續(xù)實(shí)施策略。
除了上面的幾種策略,我們也可以通過(guò)實(shí)現(xiàn)RejectedExecutionHandler來(lái)實(shí)現(xiàn)自己的策略。
利用Hook嵌入你的行為
ThreadPoolExecutor提供了protected類型可以被覆蓋的鉤子方法,允許用戶在任務(wù)執(zhí)行之前會(huì)執(zhí)行之后做一些事情。我們可以通過(guò)它來(lái)實(shí)現(xiàn)比如初始化ThreadLocal、收集統(tǒng)計(jì)信息、如記錄日志等操作。這類Hook如beforeExecute和afterExecute。另外還有一個(gè)Hook可以用來(lái)在任務(wù)被執(zhí)行完的時(shí)候讓用戶插入邏輯,如rerminated。
如果hook方法執(zhí)行失敗,則內(nèi)部的工作線程的執(zhí)行將會(huì)失敗或被中斷。
關(guān)閉線程池
當(dāng)線程池不在被引用并且工作線程數(shù)為0的時(shí)候,線程池將被終止。我們也可以調(diào)用shutdown來(lái)手動(dòng)終止線程池。如果我們忘記調(diào)用shutdown,為了讓線程資源被釋放,我們還可以使用keepAliveTime和allowCoreThreadTimeOut來(lái)達(dá)到目的。