ThreadPoolExecutor代碼中的注釋
ExcutorService 盡可能使用線程池里的Thread來執(zhí)行任務(wù)嵌施,通常使用Excutors工廠方法進(jìn)行配置。
線程池通常處理兩個(gè)不同的問題:它們會(huì)在執(zhí)行大量異步任務(wù)的時(shí)候浙巫,通過減少每個(gè)任務(wù)調(diào)度的開銷來提高性能哩盲,當(dāng)執(zhí)行一組任務(wù)的時(shí)候,線程池提供(包括線程在內(nèi))資源調(diào)度的方法狈醉。每一個(gè)ThreadPoolExecutor也可以做一些統(tǒng)計(jì)工作廉油。例如統(tǒng)計(jì)執(zhí)行完的任務(wù)數(shù)。ThreadPoolExecutor為了適應(yīng)廣泛的使用場(chǎng)景苗傅,提供了許多可調(diào)參數(shù)和回調(diào)方法抒线。
我們也可以用一些Excutors提供的工廠方法來創(chuàng)建線程池。
Executors.newCachedThreadPool()
:提供無邊界的線程池渣慕,提供自動(dòng)回收線程的功能嘶炭。
Executors.newFixedThreadPool(int)
:確定大小的線程池。Executors.newSingleThreadExecutor()
:提供一個(gè)后臺(tái)線程逊桦。
corePoolSize(核心線程數(shù))和maximumPoolSize(最大線程數(shù)):
ThreadPoolExecutor會(huì)自動(dòng)調(diào)整線程池的大小眨猎,根據(jù)這兩個(gè)值,當(dāng)通過execute(java.lang.Runnable)
方法提交一個(gè)任務(wù)時(shí)强经,當(dāng) 小于corePoolSize數(shù)量的線程在線程池里運(yùn)行睡陪,即使這些線程處于閑置狀態(tài),也會(huì)創(chuàng)建一個(gè)線程來執(zhí)行這個(gè)新任務(wù)匿情。如果線程池中有多于corePoolSize兰迫,小于maximumPoolSize個(gè)線程正在運(yùn)行,那么只有當(dāng)?shù)却?duì)列滿了才會(huì)創(chuàng)建新的線程去執(zhí)行新任務(wù)炬称。
通過將corePoolSize和maximumPoolSize設(shè)置成一樣的值來實(shí)現(xiàn)固定大小的線程池汁果。
通過將maximumPoolSize設(shè)置成Integer.MAX_VALUE,實(shí)現(xiàn)無限大的線程池玲躯,可以容納任意數(shù)量的并發(fā)任務(wù)据德。這兩個(gè)值也可以通過setCorePoolSize(int)
和setMaximumPoolSize(int)
來動(dòng)態(tài)修改。
根據(jù)需求來構(gòu)造線程池:
在默認(rèn)情況下跷车,核心線程只有在新任務(wù)來臨的時(shí)候才被初始化創(chuàng)建出來棘利,但是當(dāng)線程池在創(chuàng)建初期等待隊(duì)列就非空,可以通過實(shí)現(xiàn)prestartCoreThread()
和prestartAllCoreThreads()
方法來預(yù)啟動(dòng)核心線程姓赤。
創(chuàng)建新線程:
可以通過ThreadFactory
創(chuàng)建新的線程赡译。如果沒有指定,會(huì)使用Executors.defaultThreadFactory()
創(chuàng)建新線程不铆。使用默認(rèn)ThreadFactory
創(chuàng)建的線程會(huì)在同一個(gè)java.lang.ThreadGroup
中蝌焚,且同樣的優(yōu)先級(jí)(NORM_PRORITY)裹唆,且無daemon狀態(tài)。通過制定不同的ThreadFactory
只洒,可以改變線程的名稱许帐、線程group、優(yōu)先級(jí)毕谴、daemon狀態(tài)等成畦。當(dāng)ThreadFactory
創(chuàng)建失敗時(shí),會(huì)返回null涝开,線程池會(huì)繼續(xù)運(yùn)轉(zhuǎn)循帐,但是新的任務(wù)可能不會(huì)執(zhí)行。線程需要有modifyThread運(yùn)行時(shí)權(quán)限舀武,如果沒有這個(gè)權(quán)限拄养,服務(wù)會(huì)被降級(jí):這會(huì)導(dǎo)致一些配置不會(huì)立即生效,關(guān)閉池有可能保持在終止?fàn)顟B(tài)而非完成银舱。
Keep-alive times(線程空閑時(shí)間):
當(dāng)線程數(shù)大于核心線程數(shù)瘪匿,當(dāng)輔助線程被掛起時(shí)間超過線程空閑時(shí)間,輔助線程會(huì)被終止寻馏。這是一種當(dāng)線程池空閑時(shí)棋弥,資源重用的手段,當(dāng)之后線程池再度忙起來诚欠,會(huì)重新創(chuàng)建新的線程顽染。這個(gè)參數(shù)可以通過setKeepAliveTime(long,java.util.concurrent.TimeUnit)
方法動(dòng)態(tài)修改。
通過setKeepAliveTime(Long.MAX_VALUE,TimeUnit.NANOSECONDS)
聂薪,有效禁止掛起的線程在終止之前被關(guān)閉家乘。
在默認(rèn)情況下蝗羊,這個(gè)參數(shù)只對(duì)輔助線程有約束作用藏澳,通過調(diào)用allowCoreThreadTimeOut(boolean)
可以講參數(shù)的作用范圍擴(kuò)大到核心線程。
任務(wù)等待隊(duì)列:
等待隊(duì)列的等待策略會(huì)影響池子大小耀找。
- 如果正在運(yùn)行的線程數(shù)小于核心線程翔悠,Executor傾向于創(chuàng)建新線程執(zhí)行任務(wù),而非排隊(duì)等待野芒。
- 如果核心線程已滿或者有更多的線程在運(yùn)行蓄愁,Executor傾向于等待,而非創(chuàng)建新的線程狞悲。
- 如果等待隊(duì)列已滿撮抓,當(dāng)池子沒滿(線程數(shù)小于
maximumPoolSize
的情況),會(huì)創(chuàng)建新的線程摇锋,如果池子也滿了丹拯,任務(wù)將會(huì)被拒絕站超。
等待隊(duì)列的一些策略:
- 直接傳遞:
SynchronousQueue
,當(dāng)有任務(wù)來的時(shí)候乖酬,不會(huì)入隊(duì)列死相,如果沒有線程可以立即執(zhí)行這個(gè)任務(wù)時(shí),嘗試入隊(duì)列會(huì)導(dǎo)致失敗咬像,因此會(huì)嘗試創(chuàng)建新線程算撮。這種策略會(huì)防止有依賴關(guān)系的請(qǐng)求相互等待的情況。直接傳遞通常要求池子大小無限大县昂,以防止新任務(wù)來臨時(shí)rejected肮柜,也滿足當(dāng)任務(wù)到達(dá)的速度比運(yùn)行速度要快時(shí)池子大小無限擴(kuò)大的需求。 - 容量無限制的等待隊(duì)列:
LinkedBlockingQueue
倒彰,當(dāng)所有的核心線程處于繁忙狀態(tài)時(shí)素挽,新來的任務(wù)會(huì)入隊(duì)。因此不會(huì)有輔助線程被創(chuàng)建狸驳,maximumPoolSize
也不會(huì)起作用预明。這種隊(duì)列適用于任務(wù)之間是相互獨(dú)立的情況。例如:在web服務(wù)器中消除請(qǐng)求激增的情況下耙箍,這種隊(duì)列可以滿足請(qǐng)求排隊(duì)撰糠。 - 有容量限制的等待隊(duì)列:
ArrayBlockingQueue
可以通過設(shè)置maximumPoolSizes
,預(yù)防資源枯竭辩昆,但是這種隊(duì)列難以調(diào)試和控制阅酪。隊(duì)列的容量和池子的容量是相互影響的:用大隊(duì)列小池子會(huì)最小化CPU和系統(tǒng)資源的使用,并且可以節(jié)省context切換的成本汁针,但是會(huì)降低吞吐量术辐。當(dāng)一個(gè)任務(wù)經(jīng)常阻塞,系統(tǒng)會(huì)安排比預(yù)計(jì)的更多的時(shí)間給更多的線程施无。用小隊(duì)列大池子辉词,CPU會(huì)一直處于busy狀態(tài),這樣會(huì)導(dǎo)致調(diào)度繁忙猾骡,因?yàn)樽罱K也會(huì)減少吞吐量瑞躺。
被拒絕的任務(wù):
當(dāng)Executor已經(jīng)被干掉,或者等待隊(duì)列和池子的容量到達(dá)上限時(shí)兴想,執(zhí)行execute(java.lang.Runnable)
方法幢哨,提交新任務(wù)會(huì)被拒絕。這種情況會(huì)執(zhí)行RejectedExecutionHandler.rejectedExecution(java.lang.Runnable,java.util.concurrent.ThreadPoolExecutor)
方法嫂便。
當(dāng)任務(wù)被拒絕時(shí)捞镰,有四種預(yù)定義的策略可用:
-
ThreadPoolExecutor.AbortPolicy
會(huì)拋出RejectedExecutionException
異常。 -
ThreadPoolExecutor.CallerRunsPolicy
執(zhí)行execute方法的線程執(zhí)行任務(wù),這提供了反饋控制機(jī)制放慢新任務(wù)的提交速度岸售。 -
ThreadPoolExecutor.DiscardOldestPolicy
几迄,任務(wù)被忽略。 -
ThreadPoolExecutor.DiscardOldestPolicy
冰评,如果executor沒有被干掉映胁,等待隊(duì)列中京痢,位于head的任務(wù)會(huì)被忽略飞崖,然后重試execute可都,如果還是被拒絕舔庶,會(huì)重復(fù)此過程砸捏。
也可以定義其他種類的RejectedExecutionHandler
假栓,需要注意有容量限制的隊(duì)列和池子的情況柠硕。
鉤子方法:
可以在每個(gè)任務(wù)的beforeExecute(java.lang.Thread,java.lang.Runnable)
和afterExecute(java.lang.Runnable,java.lang.Throwable)
方法中執(zhí)行一些代碼飒货。例如:可以統(tǒng)計(jì)數(shù)據(jù)妖枚,打log廷臼,可以重啟ThreadLocals。
terminated()
會(huì)在Executor完全被干掉之后執(zhí)行绝页。
如果這些鉤子方法中出異常荠商,task會(huì)執(zhí)行失敗,或者線程會(huì)被意外終止掉续誉。
隊(duì)列的維護(hù):
getQueue()
:可以獲取工作隊(duì)列莱没。
remove(java.lang.Runnable)
和purge()
:當(dāng)大量的任務(wù)被取消的時(shí)候,可以幫助內(nèi)存回收酷鸦,
Finalization:
當(dāng)一個(gè)池子不再被引用且池子中沒有剩下的線程時(shí)饰躲,會(huì)被自動(dòng)干掉。如果擔(dān)心用戶忘記調(diào)用shutdown()
臼隔,可以設(shè)置合適的keep-alive時(shí)間嘹裂,并且使用一個(gè)核心線程數(shù)為0的線程池,或者調(diào)用allowCoreThreadTimeOut(boolean)
方法來保證工作線程會(huì)最終被干掉摔握。
構(gòu)造方法
public ThreadPoolExecutor(int corePoolSize,//核心線程數(shù)
int maximumPoolSize,//最大線程數(shù)
long keepAliveTime,//最長(zhǎng)空閑時(shí)間
TimeUnit unit,//時(shí)間單位
BlockingQueue<Runnable> workQueue,//等待隊(duì)列
ThreadFactory threadFactory, //輔助線程生成策略
RejectedExecutionHandler handler)//被拒絕的任務(wù)處理方式
BlockQueue
常用的方法
入隊(duì):
offer(anObject)
:表示如果可能的話寄狼,將anObject加到BlockingQueue里,即如果BlockingQueue可以容納盒发,則返回true例嘱,否則返回false(本方法不阻塞當(dāng)前執(zhí)行方法的線程)
offer(E o, long timeout, TimeUnit unit)
,可以設(shè)定等待的時(shí)間宁舰,如果在指定的時(shí)間內(nèi),還不能往隊(duì)列中加入BlockingQueue奢浑,則返回失敗蛮艰。
put(anObject)
:把a(bǔ)nObject加到BlockingQueue里,如果BlockQueue沒有空間,則調(diào)用此方法的線程被阻斷直到BlockingQueue里面有空間再繼續(xù)雀彼。出隊(duì):
poll(time)
:取走BlockingQueue里排在首位的對(duì)象,若不能立即取出,則可以等time參數(shù)規(guī)定的時(shí)間壤蚜,取不到時(shí)返回null即寡;
poll(long timeout, TimeUnit unit)
:從BlockingQueue取出一個(gè)隊(duì)首的對(duì)象,如果在指定時(shí)間內(nèi)袜刷,隊(duì)列一旦有數(shù)據(jù)可取聪富,則立即返回隊(duì)列中的數(shù)據(jù)。否則知道時(shí)間超時(shí)還沒有數(shù)據(jù)可取著蟹,返回失敗墩蔓。
take()
:取走BlockingQueue里排在首位的對(duì)象,若BlockingQueue為空,阻斷進(jìn)入等待狀態(tài)直到BlockingQueue有新的數(shù)據(jù)被加入萧豆;
drainTo()
:一次性從BlockingQueue獲取所有可用的數(shù)據(jù)對(duì)象(還可以指定獲取數(shù)據(jù)的個(gè)數(shù))奸披,通過該方法,可以提升獲取數(shù)據(jù)效率涮雷;不需要多次分批加鎖或釋放鎖阵面。
子類
-
ArrayBlockingQueue
ArrayBlockingQueue在生產(chǎn)者放入數(shù)據(jù)和消費(fèi)者獲取數(shù)據(jù),都是共用同一個(gè)鎖對(duì)象洪鸭,由此也意味著兩者無法真正并行運(yùn)行样刷,這點(diǎn)尤其不同于LinkedBlockingQueue;按照實(shí)現(xiàn)原理來分析览爵,ArrayBlockingQueue完全可以采用分離鎖颂斜,從而實(shí)現(xiàn)生產(chǎn)者和消費(fèi)者操作的完全并行運(yùn)行。Doug Lea之所以沒這樣去做拾枣,也許是因?yàn)锳rrayBlockingQueue的數(shù)據(jù)寫入和獲取操作已經(jīng)足夠輕巧沃疮,以至于引入獨(dú)立的鎖機(jī)制,除了給代碼帶來額外的復(fù)雜性外梅肤,其在性能上完全占不到任何便宜司蔬。 ArrayBlockingQueue和LinkedBlockingQueue間還有一個(gè)明顯的不同之處在于,前者在插入或刪除元素時(shí)不會(huì)產(chǎn)生或銷毀任何額外的對(duì)象實(shí)例姨蝴,而后者則會(huì)生成一個(gè)額外的Node對(duì)象俊啼。這在長(zhǎng)時(shí)間內(nèi)需要高效并發(fā)地處理大批量數(shù)據(jù)的系統(tǒng)中,其對(duì)于GC的影響還是存在一定的區(qū)別左医。而在創(chuàng)建ArrayBlockingQueue時(shí)授帕,我們還可以控制對(duì)象的內(nèi)部鎖是否采用公平鎖,默認(rèn)采用非公平鎖浮梢。 -
LinkedBlockingQueue
對(duì)于生產(chǎn)者端和消費(fèi)者端分別采用了獨(dú)立的鎖來控制數(shù)據(jù)同步跛十,這也意味著在高并發(fā)的情況下生產(chǎn)者和消費(fèi)者可以并行地操作隊(duì)列中的數(shù)據(jù),以此來提高整個(gè)隊(duì)列的并發(fā)性能秕硝。 -
SynchronousQueue
一種無緩沖的等待隊(duì)列芥映,類似于無中介的直接交易,有點(diǎn)像原始社會(huì)中的生產(chǎn)者和消費(fèi)者,生產(chǎn)者拿著產(chǎn)品去集市銷售給產(chǎn)品的最終消費(fèi)者奈偏,而消費(fèi)者必須親自去集市找到所要商品的直接生產(chǎn)者坞嘀,如果一方?jīng)]有找到合適的目標(biāo),那么對(duì)不起惊来,大家都在集市等待丽涩。相對(duì)于有緩沖的BlockingQueue來說,少了一個(gè)中間經(jīng)銷商的環(huán)節(jié)(緩沖區(qū))裁蚁,如果有經(jīng)銷商矢渊,生產(chǎn)者直接把產(chǎn)品批發(fā)給經(jīng)銷商,而無需在意經(jīng)銷商最終會(huì)將這些產(chǎn)品賣給那些消費(fèi)者厘擂,由于經(jīng)銷商可以庫(kù)存一部分商品昆淡,因此相對(duì)于直接交易模式,總體來說采用中間經(jīng)銷商的模式會(huì)吞吐量高一些(可以批量買賣)刽严;但另一方面昂灵,又因?yàn)榻?jīng)銷商的引入,使得產(chǎn)品從生產(chǎn)者到消費(fèi)者中間增加了額外的交易環(huán)節(jié)舞萄,單個(gè)產(chǎn)品的及時(shí)響應(yīng)性能可能會(huì)降低眨补。
聲明一個(gè)SynchronousQueue有兩種不同的方式,它們之間有著不太一樣的行為倒脓。公平模式和非公平模式的區(qū)別:
如果采用公平模式:SynchronousQueue會(huì)采用公平鎖撑螺,并配合一個(gè)FIFO隊(duì)列來阻塞多余的生產(chǎn)者和消費(fèi)者,從而體系整體的公平策略崎弃;
但如果是非公平模式(SynchronousQueue默認(rèn)):SynchronousQueue采用非公平鎖甘晤,同時(shí)配合一個(gè)LIFO隊(duì)列來管理多余的生產(chǎn)者和消費(fèi)者,而后一種模式饲做,如果生產(chǎn)者和消費(fèi)者的處理速度有差距线婚,則很容易出現(xiàn)饑渴的情況,即可能有某些生產(chǎn)者或者是消費(fèi)者的數(shù)據(jù)永遠(yuǎn)都得不到處理盆均。 -
DelayQueue
DelayQueue中的元素只有當(dāng)其指定的延遲時(shí)間到了塞弊,才能夠從隊(duì)列中獲取到該元素。DelayQueue是一個(gè)沒有大小限制的隊(duì)列泪姨,因此往隊(duì)列中插入數(shù)據(jù)的操作(生產(chǎn)者)永遠(yuǎn)不會(huì)被阻塞游沿,而只有獲取數(shù)據(jù)的操作(消費(fèi)者)才會(huì)被阻塞。
使用場(chǎng)景:
DelayQueue使用場(chǎng)景較少肮砾,但都相當(dāng)巧妙诀黍,常見的例子比如使用一個(gè)DelayQueue來管理一個(gè)超時(shí)未響應(yīng)的連接隊(duì)列。 -
PriorityBlockingQueue
基于優(yōu)先級(jí)的阻塞隊(duì)列(優(yōu)先級(jí)的判斷通過構(gòu)造函數(shù)傳入的Compator對(duì)象來決定)唇敞,但需要注意的是PriorityBlockingQueue并不會(huì)阻塞數(shù)據(jù)生產(chǎn)者蔗草,而只會(huì)在沒有可消費(fèi)的數(shù)據(jù)時(shí)咒彤,阻塞數(shù)據(jù)的消費(fèi)者疆柔。因此使用的時(shí)候要特別注意咒精,生產(chǎn)者生產(chǎn)數(shù)據(jù)的速度絕對(duì)不能快于消費(fèi)者消費(fèi)數(shù)據(jù)的速度,否則時(shí)間一長(zhǎng)旷档,會(huì)最終耗盡所有的可用堆內(nèi)存空間模叙。在實(shí)現(xiàn)PriorityBlockingQueue時(shí),內(nèi)部控制線程同步的鎖采用的是公平鎖鞋屈。