ThreadPoolExecutor線程池主要參數(shù)你都知道嗎?

ThreadPoolExecutor線程池是并發(fā)編程中用的比較多的一個類,項(xiàng)目和面試的時候經(jīng)常會用到,所以了解一下是很有必要的。

什么是線程池

線程池是池化技術(shù)的一種。它有以下優(yōu)勢:

  • 重復(fù)利用線程,降低線程創(chuàng)建和銷毀帶來的資源消耗
  • 統(tǒng)一管理線程,線程的創(chuàng)建和銷毀都由線程池進(jìn)行管理
  • 提高響應(yīng)速度,線程創(chuàng)建已經(jīng)完成,任務(wù)來到可直接處理,省去了創(chuàng)建時間

參數(shù)

ThreadPoolExecutor的最多參數(shù)的構(gòu)造函數(shù)如下:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) 

ThreadPoolExecutor的創(chuàng)建主要參數(shù)有7個,接下來將進(jìn)行一一介紹。

最大線程數(shù)(MaximumPoolSize)和核心線程數(shù)(CorePoolSize)

  • 最大線程數(shù)(MaximumPoolSize):線程池運(yùn)行的最大線程數(shù)量,受屬性CAPACITY的限制,最大為(2^29)-1(約5億)
  • 核心線程數(shù)(CorePoolSize):線程池中保持最小活動數(shù)的線程數(shù)量,并且不允許超時,除非調(diào)用allowCoreThreadTimeOut方法,這個時候最小值是0。
  • 當(dāng)線程池線程數(shù)量小于核心線程數(shù)時,一個新的任務(wù)請求被提交上來時,不管其他線程是否處于空閑狀態(tài),都會新建一個線程來處理這個請求。
    如果在運(yùn)行的線程數(shù)數(shù)量超過核心線程數(shù)但是小于最大線程數(shù),并且工作隊(duì)列已滿,將創(chuàng)建一個線程處理這個請求。

默認(rèn)情況下,當(dāng)一個任務(wù)請求時,核心線程數(shù)才會被創(chuàng)建和啟動,但是也可以通過prestartCoreThread啟動一個核心線程或者prestartAllCoreThread啟動所有核心線程。

創(chuàng)建新的線程(ThreadFactory)

ThreadFactory用來創(chuàng)建線程。如果沒有指定ThreadFactory的話,默認(rèn)會使用Executors#defaultThreadFactory 來創(chuàng)建線程,并且這些線程都是在同一個ThreadGroup并且都是非守護(hù)線程狀態(tài)(non-daemon status)并且擁有相同的優(yōu)先級(NORM_PRIORITY)。 如果指定了ThreadFactory 可以修改ThreadGroup和線程的名稱、守護(hù)狀態(tài)、優(yōu)先級等。
ThreadFactory如果調(diào)用newThread(Runnable r)方法返回null則創(chuàng)建線程失敗,線程池會繼續(xù)運(yùn)行但可能不會執(zhí)行任何任務(wù)。
線程應(yīng)該擁有"modifyThread"權(quán)限,如果工作線程或者其他線程沒有擁有這個權(quán)限,服務(wù)可能會降級,配置更改可能不會及時生效,關(guān)閉線程池可能保持在可能終止但未完成的狀態(tài)。

存活時間(Keep-alive times)

存活時間(Keep-alive times):空閑線程等待工作的超時時間(以納秒為單位)
如果當(dāng)前線程池中的線程數(shù)超過了核心線程數(shù),超出的部分線程如果空閑的時長大于存活時長,那么他們將會被終止運(yùn)行。當(dāng)線程池不被頻繁使用的時候,這提供了一種減少資源消耗的方法。存活時間可以通過setKeepAliveTime(long, TimeUnit)進(jìn)行修改,使用 setKeepAliveTime(Long.MAX_VALUE, NANOSECONDS)有效地禁止空閑線程在關(guān)閉之前終止。默認(rèn)情況下,存活策略只適用于當(dāng)前線程數(shù)超過核心線程數(shù)的情況下。
但是使用方法allowCoreThreadTimeOut(boolean)也可以將這個超時策略應(yīng)用到核心線程,只要keepAliveTime值不為零。

時間單位(TimeUnit)

TimeUnit 是存活時間的單位。

阻塞隊(duì)列(BlockingQueue)

任何實(shí)現(xiàn)了BlockingQueue接口的實(shí)現(xiàn)類都可以用來傳輸和保存提交的任務(wù),阻塞隊(duì)列的使用和線程池大小相關(guān):

  1. 如果運(yùn)行的線程少于核心線程數(shù), Executor總是傾向于添加一個新線程而不是排隊(duì)
  2. 如果核心線程數(shù)或更多線程正在運(yùn)行(不超過最大線程數(shù)),Executor總是傾向于排隊(duì)請求,而不是添加一個新線程
  3. 如果沒有達(dá)到最大線程數(shù)并且隊(duì)列未滿,將創(chuàng)建新的線程執(zhí)行任務(wù),如果線程數(shù)大于最大線程數(shù),任務(wù)將會被拒絕

三種排隊(duì)策略

  1. 直接傳遞
    工作隊(duì)列的一個很好的默認(rèn)選擇是 SynchronousQueue,它將任務(wù)交給線程而不用其他方式持有它們。一個新的任務(wù)嘗試排隊(duì)時,如果沒有可供使用的線程運(yùn)行它時將會創(chuàng)建一個新的線程。該策略避免了鎖定處理可能具有內(nèi)部依賴關(guān)系的請求集,直接傳遞通常需要無界的最大線程池來避免新的任務(wù)提交。這反過來又承認(rèn)了當(dāng)命令的平均到達(dá)速度快于它們的處理速度時,線程無限增長的可能性。

  2. 無界隊(duì)列
    無界隊(duì)列是一個沒有預(yù)定義容量的隊(duì)列,使用無界隊(duì)列例如LinkedBlockingQueue將導(dǎo)致新任務(wù)一直在等待,當(dāng)核心線程數(shù)的線程處于工作狀態(tài)時。因此,不會有超過核心線程數(shù)的線程被創(chuàng)建,也就是說最大線程數(shù)是不起作用的。當(dāng)任務(wù)之間互相獨(dú)立,互不影響的時候這個選擇可能是挺合適的。例如,在web服務(wù)器中,這種隊(duì)列在消除短暫的高并發(fā)方面很有作用,它允許無界隊(duì)列增長的平均速度比處理的平均速度快。

  3. 有界隊(duì)列
    無界隊(duì)列例如ArrayBlockingQueue,它能在有限的最大線程數(shù)內(nèi)防止資源耗盡,但是它也更難調(diào)整和控制。
    隊(duì)列的大小和最大線程數(shù)可以互相替換:使用更大的隊(duì)列數(shù)量和小的線程池?cái)?shù)量能夠最小化CPU的使用、系統(tǒng)資源和上下文切換的開銷,但也人為的導(dǎo)致了低吞吐量。如果一個任務(wù)頻繁的阻塞,例如頻繁I/O,系統(tǒng)更多的時間是在頻繁的調(diào)度而不是運(yùn)行任務(wù)。使用小的隊(duì)列通常需要大的線程池?cái)?shù)量,這會讓CPU更能充分利用,但是也會遇到不可接受的調(diào)度開銷,也會降低吞吐量。

拒絕任務(wù)

在調(diào)用execute(Runnable)提交任務(wù)時,在Executor已經(jīng)關(guān)閉或者有界隊(duì)列的最大線程數(shù)和隊(duì)列滿的情況下任務(wù)會被拒絕。不論在什么情況下,execute方法調(diào)用RejectedExecutionHandler#rejectedExecution(Runnable, ThreadPoolExecutor)任務(wù)都會根據(jù)拒絕策略被拒絕。

四種拒絕策略

ThreadPoolExecutor預(yù)定義了四種拒絕策略:

  1. ThreadPoolExecutor.AbortPolicy,默認(rèn)的拒絕策略,簡單粗暴,拒絕的時候直接拋RejectedExecutionException異常
  2. ThreadPoolExecutor.CallerRunsPolicy,由調(diào)用者執(zhí)行自身execute方法來運(yùn)行提交進(jìn)來的任務(wù),從名字CallerRuns(調(diào)用者運(yùn)行)中就可以看出。它會提供一個簡單的反饋控制機(jī)制,這種策略將降低新任務(wù)被提交上來的速度。
  3. ThreadPoolExecutor.DiscardPolicy,也很簡單粗暴,直接丟棄任務(wù),不拋異常。
  4. ThreadPoolExecutor.DiscardOldestPolicy,DiscardOldest丟棄最早的任務(wù),在隊(duì)列頭部也就是最新進(jìn)入隊(duì)列的任務(wù)會被丟棄,然后嘗試提交新任務(wù),如果提交失敗會繼續(xù)重復(fù)以上步驟。

也可以自己實(shí)現(xiàn)RejectedExecutionHandler接口,并重寫rejectedExecution方法來自定義拒絕策略。

通俗解釋

關(guān)于上面的參數(shù)我試著通俗的說一下,希望我說的能讓你明白。
假如現(xiàn)在有一家外包公司(ThreadPoolExecutor),公司的核心開發(fā)(corePoolSize)有5個人,公司最多容納(maximumPoolSize)10個開發(fā),現(xiàn)在公司接了一個項(xiàng)目,核心開發(fā)還忙的過來,就將這個項(xiàng)目給其中一個核心開發(fā)做,慢慢的銷售人員接的項(xiàng)目越來越多,5個核心開發(fā)都在做項(xiàng)目沒時間再做新的項(xiàng)目,公司為了節(jié)省開支新來的項(xiàng)目只能先接過來暫時積壓(BlockingQueue)起來,但是一直積壓也不是個事情,客戶也會一直催,公司頂住最多只能積壓5個,積壓到5個之后公司也還能容納5個開發(fā),不得不再招人處理新的項(xiàng)目。當(dāng)公司發(fā)展的越來越好,接的項(xiàng)目也越來越多這10個開發(fā)也忙不過來了,有新的項(xiàng)目再進(jìn)來就只能通過各種方式拒絕(RejectedExecutionHandler)了。再后來因?yàn)橐咔樵颍灸芙拥降捻?xiàng)目也越來越少了,開發(fā)人員很多(Thread)已經(jīng)沒事兒可做了,大概過了兩周時間(keepAliveTime),公司又為了節(jié)省開支就把這些空閑下來的非核心開發(fā)給開了。當(dāng)然,核心開發(fā)也不是說一定不能動也是可以開的(allowCoreThreadTimeOut(true)),只不過肯定是優(yōu)先考慮非核心人員。
有人說了,項(xiàng)目多的時候?yàn)樯恫粩U(kuò)大公司規(guī)模呢?
首先,公司老板最多也就有養(yǎng)這幾個員工的的能力,養(yǎng)的多了老板也吃不消,多招一個人可能也不會使工作效率提高,反而可能拖累其他開發(fā)的進(jìn)度,能養(yǎng)幾個員工也是經(jīng)過老板深思熟慮加以往的經(jīng)驗(yàn)總結(jié)得出的結(jié)果。

線程池大致流程圖


在這里插入圖片描述

鉤子方法(Hook methods)

ThreadPoolExecutor提供了protected權(quán)限的beforeExecute(Thread, Runnable)和afterExecute(Runnable, Throwable)方法供子類重寫,這兩個方法可以在任務(wù)執(zhí)行的前后調(diào)用。這些可以用來操作執(zhí)行環(huán)境,例如:重新初始化ThreadLocal、收集統(tǒng)計(jì)信息或者添加日志,類似靜態(tài)代理。另外terminated方法也可以被重寫用來處理特殊情況,當(dāng)Executor完全被終止時。
如果鉤子方法或者回調(diào)方法拋異常,工作線程可能會執(zhí)行失敗或者突然終止。

隊(duì)列維護(hù)

可以通過方法getQueue獲取工作隊(duì)列并進(jìn)行監(jiān)控和調(diào)試,如果是為了其他目的則強(qiáng)烈反對這么做。當(dāng)大量的任務(wù)被取消時,方法remove(Runnable)和purge可用于儲存回收。

線程池關(guān)閉

線程池如果在系統(tǒng)中沒有再被引用并且沒有線程在使用時將會被自動關(guān)閉,如果你想確保未被使用的線程池被回收即使用戶忘記調(diào)用shutdown方法,你必須通過設(shè)置合適的存活時間、使用零核心線程的下限或者設(shè)置#allowCoreThreadTimeOut(boolean)來使未被使用的線程最終關(guān)閉。

能力一般,水平有限,如有錯誤,請多指出。
如果對你有用點(diǎn)個關(guān)注給個贊唄,更多文章可以關(guān)注一下我的微信公眾號suncodernote

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容