- 什么是線程池
- 為什么要使用線程池
- 線程池的處理邏輯
- 如何使用線程池
- 如何合理配置線程池的大小
- 結(jié)語(yǔ)
什么是線程池
線程池菩鲜,顧名思義就是裝線程的池子。其用途是為了幫我們重復(fù)管理線程集晚,避免創(chuàng)建大量的線程增加開(kāi)銷(xiāo)晌砾,提高響應(yīng)速度攀甚。
為什么要用線程池
作為一個(gè)嚴(yán)謹(jǐn)?shù)墓コ仟{吞瞪,不會(huì)希望別人看到我們的代碼就開(kāi)始吐槽馁启,new Thread().start()會(huì)讓代碼看起來(lái)混亂臃腫,并且不好管理和維護(hù)芍秆,那么我們就需要用到了線程池惯疙。
在編程中經(jīng)常會(huì)使用線程來(lái)異步處理任務(wù)翠勉,但是每個(gè)線程的創(chuàng)建和銷(xiāo)毀都需要一定的開(kāi)銷(xiāo)。如果每次執(zhí)行一個(gè)任務(wù)都需要開(kāi)一個(gè)新線程去執(zhí)行霉颠,則這些線程的創(chuàng)建和銷(xiāo)毀將消耗大量的資源对碌;并且線程都是“各自為政”的,很難對(duì)其進(jìn)行控制蒿偎,更何況有一堆的線程在執(zhí)行俭缓。線程池為我們做的,就是線程創(chuàng)建之后為我們保留酥郭,當(dāng)我們需要的時(shí)候直接拿來(lái)用,省去了重復(fù)創(chuàng)建銷(xiāo)毀的過(guò)程愿吹。
線程池的處理邏輯
線程池ThreadPoolExecutor構(gòu)造函數(shù)
//五個(gè)參數(shù)的構(gòu)造函數(shù)
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue)
//六個(gè)參數(shù)的構(gòu)造函數(shù)-1
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory)
//六個(gè)參數(shù)的構(gòu)造函數(shù)-2
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler)
//七個(gè)參數(shù)的構(gòu)造函數(shù)
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
雖然參數(shù)多不从,只是看著嚇人,其實(shí)很好理解犁跪,下面會(huì)一一解答椿息。
我們拿最多參數(shù)的來(lái)說(shuō):
1.corePoolSize -> 該線程池中核心線程數(shù)最大值
核心線程:在創(chuàng)建完線程池之后,核心線程先不創(chuàng)建坷衍,在接到任務(wù)之后創(chuàng)建核心線程寝优。并且會(huì)一直存在于線程池中(即使這個(gè)線程啥都不干),有任務(wù)要執(zhí)行時(shí)枫耳,如果核心線程沒(méi)有被占用乏矾,會(huì)優(yōu)先用核心線程執(zhí)行任務(wù)。數(shù)量一般情況下設(shè)置為CPU核數(shù)的二倍即可迁杨。
2.maximumPoolSize -> 該線程池中線程總數(shù)最大值
線程總數(shù)=核心線程數(shù)+非核心線程數(shù)
非核心線程:簡(jiǎn)單理解钻心,即核心線程都被占用,但還有任務(wù)要做铅协,就創(chuàng)建非核心線程
3.keepAliveTime -> 非核心線程閑置超時(shí)時(shí)長(zhǎng)
這個(gè)參數(shù)可以理解為捷沸,任務(wù)少,但池中線程多狐史,非核心線程不能白養(yǎng)著痒给,超過(guò)這個(gè)時(shí)間不工作的就會(huì)被干掉,但是核心線程會(huì)保留骏全。
4.TimeUnit -> keepAliveTime的單位
TimeUnit是一個(gè)枚舉類(lèi)型苍柏,其包括:
NANOSECONDS : 1微毫秒 = 1微秒 / 1000
MICROSECONDS : 1微秒 = 1毫秒 / 1000
MILLISECONDS : 1毫秒 = 1秒 /1000
SECONDS : 秒
MINUTES : 分
HOURS : 小時(shí)
DAYS : 天
5.BlockingQueue workQueue -> 線程池中的任務(wù)隊(duì)列
默認(rèn)情況下,任務(wù)進(jìn)來(lái)之后先分配給核心線程執(zhí)行姜贡,核心線程如果都被占用序仙,并不會(huì)立刻開(kāi)啟非核心線程執(zhí)行任務(wù),而是將任務(wù)插入任務(wù)隊(duì)列等待執(zhí)行鲁豪,核心線程會(huì)從任務(wù)隊(duì)列取任務(wù)來(lái)執(zhí)行潘悼,任務(wù)隊(duì)列可以設(shè)置最大值律秃,一旦插入的任務(wù)足夠多,達(dá)到最大值治唤,才會(huì)創(chuàng)建非核心線程執(zhí)行任務(wù)棒动。
常見(jiàn)的workQueue有四種:
1.SynchronousQueue:這個(gè)隊(duì)列接收到任務(wù)的時(shí)候,會(huì)直接提交給線程處理宾添,而不保留它船惨,如果所有線程都在工作怎么辦?那就新建一個(gè)線程來(lái)處理這個(gè)任務(wù)缕陕!所以為了保證不出現(xiàn)<線程數(shù)達(dá)到了maximumPoolSize
而不能新建線程>的錯(cuò)誤粱锐,使用這個(gè)類(lèi)型隊(duì)列的時(shí)候,maximumPoolSize
一般指定成Integer.MAX_VALUE扛邑,即無(wú)限大
2.LinkedBlockingQueue:這個(gè)隊(duì)列接收到任務(wù)的時(shí)候怜浅,如果當(dāng)前已經(jīng)創(chuàng)建的核心線程數(shù)小于線程池的核心線程數(shù)上限,則新建線程(核心線程)處理任務(wù)蔬崩;如果當(dāng)前已經(jīng)創(chuàng)建的核心線程數(shù)等于核心線程數(shù)上限恶座,則進(jìn)入隊(duì)列等待。由于這個(gè)隊(duì)列沒(méi)有最大值限制沥阳,即所有超過(guò)核心線程數(shù)的任務(wù)都將被添加到隊(duì)列中跨琳,這也就導(dǎo)致了maximumPoolSize
的設(shè)定失效,因?yàn)榭偩€程數(shù)永遠(yuǎn)不會(huì)超過(guò)corePoolSize
3.ArrayBlockingQueue:可以限定隊(duì)列的長(zhǎng)度桐罕,接收到任務(wù)的時(shí)候脉让,如果沒(méi)有達(dá)到corePoolSize
的值,則新建線程(核心線程)執(zhí)行任務(wù)功炮,如果達(dá)到了侠鳄,則入隊(duì)等候,如果隊(duì)列已滿死宣,則新建線程(非核心線程)執(zhí)行任務(wù)伟恶,又如果總線程數(shù)到了maximumPoolSize
,并且隊(duì)列也滿了毅该,則發(fā)生錯(cuò)誤博秫,或是執(zhí)行實(shí)現(xiàn)定義好的飽和策略
4.DelayQueue:隊(duì)列內(nèi)元素必須實(shí)現(xiàn)Delayed接口,這就意味著你傳進(jìn)去的任務(wù)必須先實(shí)現(xiàn)Delayed接口眶掌。這個(gè)隊(duì)列接收到任務(wù)時(shí)挡育,首先先入隊(duì),只有達(dá)到了指定的延時(shí)時(shí)間朴爬,才會(huì)執(zhí)行任務(wù)
6.ThreadFactory threadFactory -> 創(chuàng)建線程的工廠
可以用線程工廠給每個(gè)創(chuàng)建出來(lái)的線程設(shè)置名字即寒。一般情況下無(wú)須設(shè)置該參數(shù)。
7.RejectedExecutionHandler handler -> 飽和策略
這是當(dāng)任務(wù)隊(duì)列和線程池都滿了時(shí)所采取的應(yīng)對(duì)策略,默認(rèn)是AbordPolicy母赵, 表示無(wú)法處理新任務(wù)逸爵,并拋出 RejectedExecutionException 異常。此外還有3種策略凹嘲,它們分別如下师倔。
(1)CallerRunsPolicy:用調(diào)用者所在的線程來(lái)處理任務(wù)。此策略提供簡(jiǎn)單的反饋控制機(jī)制周蹭,能夠減緩新任務(wù)的提交速度趋艘。
(2)DiscardPolicy:不能執(zhí)行的任務(wù),并將該任務(wù)刪除凶朗。
(3)DiscardOldestPolicy:丟棄隊(duì)列最近的任務(wù)瓷胧,并執(zhí)行當(dāng)前的任務(wù)。
別暈棚愤,接下來(lái)上圖搓萧,相信結(jié)合圖你能大徹大悟~
如何使用線程池
說(shuō)了半天原理,接下來(lái)就要用了遇八,java為我們提供了4種線程池FixedThreadPool
、CachedThreadPool
耍休、SingleThreadExecutor
刃永、ScheduledThreadPool
,幾乎可以滿足我們大部分的需要了:
1.FixedThreadPool
可重用固定線程數(shù)的線程池羊精,超出的線程會(huì)在隊(duì)列中等待斯够,在Executors類(lèi)中我們可以找到創(chuàng)建方式:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
FixedThreadPool
的corePoolSize
和maximumPoolSize
都設(shè)置為參數(shù)nThreads,也就是只有固定數(shù)量的核心線程喧锦,不存在非核心線程读规。keepAliveTime
為0L表示多余的線程立刻終止,因?yàn)椴粫?huì)產(chǎn)生多余的線程燃少,所以這個(gè)參數(shù)是無(wú)效的束亏。FixedThreadPool
的任務(wù)隊(duì)列采用的是LinkedBlockingQueue。
創(chuàng)建線程池的方法阵具,在我們的程序中只需要碍遍,后面其他種類(lèi)的同理:
public static void main(String[] args) {
// 參數(shù)是要線程池的線程最大值
ExecutorService executorService = Executors.newFixedThreadPool(10);
}
2.CachedThreadPool
CachedThreadPool是一個(gè)根據(jù)需要?jiǎng)?chuàng)建線程的線程池
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
CachedThreadPool
的corePoolSize
是0,maximumPoolSize
是Int的最大值阳液,也就是說(shuō)CachedThreadPool
沒(méi)有核心線程怕敬,全部都是非核心線程,并且沒(méi)有上限帘皿。keepAliveTime
是60秒东跪,就是說(shuō)空閑線程等待新任務(wù)60秒,超時(shí)則銷(xiāo)毀。此處用到的隊(duì)列是阻塞隊(duì)列SynchronousQueue
,這個(gè)隊(duì)列沒(méi)有緩沖區(qū)虽填,所以其中最多只能存在一個(gè)元素,有新的任務(wù)則阻塞等待丁恭。
3.SingleThreadExecutor
SingleThreadExecutor
是使用單個(gè)線程工作的線程池。其創(chuàng)建源碼如下:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
我們可以看到總線程數(shù)和核心線程數(shù)都是1卤唉,所以就只有一個(gè)核心線程涩惑。該線程池才用鏈表阻塞隊(duì)列LinkedBlockingQueue
,先進(jìn)先出原則桑驱,所以保證了任務(wù)的按順序逐一進(jìn)行竭恬。
4.ScheduledThreadPool
ScheduledThreadPool
是一個(gè)能實(shí)現(xiàn)定時(shí)和周期性任務(wù)的線程池,它的創(chuàng)建源碼如下:
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
這里創(chuàng)建了ScheduledThreadPoolExecutor
熬的,繼承自ThreadPoolExecutor
痊硕,主要用于定時(shí)延時(shí)或者定期處理任務(wù)。ScheduledThreadPoolExecutor
的構(gòu)造如下:
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
可以看出corePoolSize
是傳進(jìn)來(lái)的固定值押框,maximumPoolSize
無(wú)限大岔绸,因?yàn)椴捎玫年?duì)列DelayedWorkQueue
是無(wú)解的,所以maximumPoolSize
參數(shù)無(wú)效橡伞。該線程池執(zhí)行如下:
當(dāng)執(zhí)行scheduleAtFixedRate
或者scheduleWithFixedDelay
方法時(shí)盒揉,會(huì)向DelayedWorkQueue
添加一個(gè)實(shí)現(xiàn)RunnableScheduledFuture
接口的ScheduledFutureTask
(任務(wù)的包裝類(lèi)),并會(huì)檢查運(yùn)行的線程是否達(dá)到corePoolSize
兑徘。如果沒(méi)有則新建線程并啟動(dòng)ScheduledFutureTask
刚盈,然后去執(zhí)行任務(wù)。如果運(yùn)行的線程達(dá)到了corePoolSize
時(shí)挂脑,則將任務(wù)添加到DelayedWorkQueue
中藕漱。DelayedWorkQueue
會(huì)將任務(wù)進(jìn)行排序,先要執(zhí)行的任務(wù)會(huì)放在隊(duì)列的前面崭闲。在跟此前介紹的線程池不同的是肋联,當(dāng)執(zhí)行完任務(wù)后,會(huì)將ScheduledFutureTask
中的time
變量改為下次要執(zhí)行的時(shí)間并放回到DelayedWorkQueue
中刁俭。
如何合理配置線程池的大小
一般需要根據(jù)任務(wù)的類(lèi)型來(lái)配置線程池大虚先浴:
如果是CPU密集型任務(wù),就需要盡量壓榨CPU牍戚,參考值可以設(shè)為 NCPU+1
如果是IO密集型任務(wù)沙兰,參考值可以設(shè)置為2*NCPU
當(dāng)然,這只是一個(gè)參考值翘魄,具體的設(shè)置還需要根據(jù)實(shí)際情況進(jìn)行調(diào)整鼎天,比如可以先將線程池大小設(shè)置為參考值,再觀察任務(wù)運(yùn)行情況和系統(tǒng)負(fù)載暑竟、資源利用率來(lái)進(jìn)行適當(dāng)調(diào)整斋射。
結(jié)語(yǔ)
java為我們提供的線程池就介紹到這了育勺,墻裂建議大家還是動(dòng)手去敲一敲,畢竟實(shí)踐過(guò)心里才有底罗岖。
作者:愛(ài)擼鐵的攻城獅
鏈接:https://juejin.im/post/5a743c526fb9a063557d7eba
來(lái)源:掘金
著作權(quán)歸作者所有涧至。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處桑包。