為什么阿里巴巴禁止使用 Executors 創(chuàng)建線程池?
點(diǎn)擊上方?程序IT圈,選擇?設(shè)為星標(biāo)
優(yōu)質(zhì)文章,每日送達(dá)
阿里巴巴開發(fā)手冊(cè)關(guān)于線程池有這樣一條規(guī)定:
線程池不允許使用 Executors 去創(chuàng)建,而是通過 ThreadPoolExecutor 的方式,這樣的處理方式讓寫的同學(xué)更加明確線程池的運(yùn)行規(guī)則,規(guī)避資源耗盡的風(fēng)險(xiǎn)。
一、線程池原理
1.1 為什么使用線程池
池化技術(shù)的思想主要是為了減少在創(chuàng)建和銷毀線程上所消耗的時(shí)間及系統(tǒng)資源的開銷,解決資源不足的問題。
1.2 線程池是如何實(shí)現(xiàn)的
本文只討論通過ThreadPoolExecutor創(chuàng)建的線程池。ThreadPoolExecutor的構(gòu)造器代碼如下,里面涉及到的主要參數(shù)有corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,threadFactory,handler。
public?ThreadPoolExecutor(int?corePoolSize,?
??????????????????????????int?maximumPoolSize,
??????????????????????????long?keepAliveTime,
??????????????????????????TimeUnit?unit,
??????????????????????????BlockingQueue?workQueue,
??????????????????????????ThreadFactory?threadFactory,
??????????????????????????RejectedExecutionHandler?handler) ?{
????if?(corePoolSize?0?||
????????maximumPoolSize?<=?0?||
????????maximumPoolSize?????????keepAliveTime?0)
????????throw?new?IllegalArgumentException();
????if?(workQueue?==?null?||?threadFactory?==?null?||?handler?==?null)
????????throw?new?NullPointerException();
????this.corePoolSize?=?corePoolSize;
????this.maximumPoolSize?=?maximumPoolSize;
????this.workQueue?=?workQueue;
????this.keepAliveTime?=?unit.toNanos(keepAliveTime);
????this.threadFactory?=?threadFactory;
????this.handler?=?handler;
}
這些參數(shù)的含義為:
corePoolSize:核心線程數(shù)maximumPoolSize:最大線程數(shù)keepAliveTime:當(dāng)線程池線程數(shù)量大于corePoolSize時(shí)候,多出來的空閑線程的存活時(shí)間unit:參數(shù)keepAliveTime的時(shí)間單位,TimeUnit枚舉類有天,小時(shí),分,秒,毫秒,微秒,納秒7種可以選擇。workQueue:線程池使用的緩沖隊(duì)列,可供選擇的有以下幾種。
| 參數(shù) | 描述 |
|---|---|
| ArrayBlockingQueue | 一個(gè)由數(shù)組結(jié)構(gòu)組成的有界阻塞隊(duì)列。 |
| LinkedBlockingQueue | 一個(gè)由鏈表結(jié)構(gòu)組成的有界阻塞隊(duì)列。常用 |
| SynchronousQueue | 一個(gè)不存儲(chǔ)元素的阻塞隊(duì)列,即直接提交給線程不保持它們。常用 |
| PriorityBlockingQueue | 一個(gè)支持優(yōu)先級(jí)排序的無界阻塞隊(duì)列。 |
| DelayQueue | 一個(gè)使用優(yōu)先級(jí)隊(duì)列實(shí)現(xiàn)的無界阻塞隊(duì)列,只有在延遲期滿時(shí)才能從中提取元素。 |
| LinkedTransferQueue | 一個(gè)由鏈表結(jié)構(gòu)組成的無界阻塞隊(duì)列。與SynchronousQueue類似,還含有非阻塞方法。 |
| LinkedBlockingDeque | 一個(gè)由鏈表結(jié)構(gòu)組成的雙向阻塞隊(duì)列。 |
threadFactory:線程工廠,主要用來創(chuàng)建線程handler:拒絕策略,拒絕處理任務(wù)時(shí)的策略,可供選擇的有以下幾種。
| 參數(shù) | 描述 |
|---|---|
| AbortPolicy | 拒絕并拋出異常。默認(rèn)的 |
| CallerRunsPolicy | 重試提交當(dāng)前的任務(wù),即再次調(diào)用運(yùn)行該任務(wù)的execute()方法。 |
| DiscardOldestPolicy | 拋棄隊(duì)列頭部(最舊)的一個(gè)任務(wù),并執(zhí)行當(dāng)前任務(wù)。 |
| DiscardPolicy | 拋棄當(dāng)前任務(wù)。 |
1.3 線程池執(zhí)行規(guī)則

執(zhí)行任務(wù)時(shí),如果線程池中的線程數(shù)量小于 corePoolSize,即使池中有空閑的線程數(shù),也會(huì)創(chuàng)建新的線程來執(zhí)行任務(wù)。線程池中的線程數(shù)量等于 corePoolSize,并且緩沖隊(duì)列未滿時(shí),則任務(wù)被放入緩沖隊(duì)列中線程池中的線程數(shù)量大于等于 corePoolSize,并且緩沖隊(duì)列已滿,同時(shí)線程數(shù)量小于maximumPoolSize,則會(huì)創(chuàng)建新的線程來執(zhí)行任務(wù)。線程池中的線程數(shù)量已滿時(shí),則執(zhí)行拒絕策略處理這些任務(wù)。
二、阿里巴巴手冊(cè)為什么禁止用Exectors創(chuàng)建線程池
Exectors提供了幾種工廠方法用來創(chuàng)建線程池,其中newCachedThreadPool(),newFixedThreadPool(),newSingleThreadExecutor()三種方法最終是通過實(shí)現(xiàn)類ThreadPoolExecutor來創(chuàng)建的。接下來一起看看這三種方法到底有什么問題,為什么阿里巴巴會(huì)禁止使用Exectors來創(chuàng)建線程池!
2.1 FixedThreadPool 解析
public?static?ExecutorService?newFixedThreadPool(int?nThreads)?{
????return?new?ThreadPoolExecutor(nThreads,?nThreads,
??????????????????????????????????0L,?TimeUnit.MILLISECONDS,
??????????????????????????????????new?LinkedBlockingQueue());
}
具體參數(shù)如下:
corePoolSize:nThreadsmaximumPoolSize:nThreadskeepAliveTime:0Lunit:毫秒workQueue:LinkedBlockingQueue,一個(gè)由鏈表結(jié)構(gòu)組成的有界阻塞隊(duì)列,并且使用了最大長度的隊(duì)列。
public?LinkedBlockingQueue()?{
????this(Integer.MAX_VALUE);
}
這種方式創(chuàng)建的線程池由于核心線程數(shù)和最大線程數(shù)相同,所以線程池中線程的數(shù)量是固定的,并且沒有限制隊(duì)列大小,所以多余的任務(wù)均會(huì)被放到隊(duì)列中排隊(duì),在資源有限時(shí)容易出現(xiàn)內(nèi)存溢出。
2.2 SingleThreadPool 解析
public?static?ExecutorService?newSingleThreadExecutor()?{
????return?new?FinalizableDelegatedExecutorService
????????(new?ThreadPoolExecutor(1,?1,
????????????????????????????????0L,?TimeUnit.MILLISECONDS,
????????????????????????????????new?LinkedBlockingQueue()));
}
具體參數(shù)如下:
corePoolSize:1maximumPoolSize:1keepAliveTime:0Lunit:毫秒workQueue:LinkedBlockingQueue,一個(gè)由鏈表結(jié)構(gòu)組成的有界阻塞隊(duì)列,并且使用了最大長度的隊(duì)列。
public?LinkedBlockingQueue()?{
????this(Integer.MAX_VALUE);
}
這種方式創(chuàng)建的線程池是單線程線程池,核心線程數(shù)和最大線程數(shù)都是1,多余的任務(wù)都將會(huì)被放到緩沖隊(duì)列中去,所以在資源優(yōu)先的情況下容易出現(xiàn)內(nèi)存溢出。
2.3 CachedThreadPool 解析
public?static?ExecutorService?newCachedThreadPool()?{
????return?new?ThreadPoolExecutor(0,?Integer.MAX_VALUE,
??????????????????????????????????60L,?TimeUnit.SECONDS,
??????????????????????????????????new?SynchronousQueue());
}
具體參數(shù)如下:
corePoolSize:0maximumPoolSize:Integer.MAX_VALUEkeepAliveTime:60Lunit:秒workQueue:SynchronousQueue,一個(gè)不存儲(chǔ)元素的阻塞隊(duì)列,即直接提交給線程不保持它們。
這種方式創(chuàng)建的線程池核心線程數(shù)為0,并且使用了SynchronousQueue隊(duì)列,這個(gè)隊(duì)列不存儲(chǔ)元素,也就是任務(wù)直接會(huì)直接通過創(chuàng)建非核心線程來執(zhí)行,核心線程數(shù)為Integer.MAX_VALUE,可以任務(wù)能無限創(chuàng)建隊(duì)列,因此在資源優(yōu)先的情況下容易發(fā)生內(nèi)存溢出。
2.4 測試OOM異常
既然我們已經(jīng)分析了三種創(chuàng)建線程池可能會(huì)出現(xiàn)OOM異常,那么我們測試一下到底會(huì)不會(huì)發(fā)生OOM呢?這里我將選擇newSingleThreadExecutor()來進(jìn)行測試,其他兩個(gè)方法測試流程也是一樣的。為了盡快出現(xiàn)OOM,我們將JVM的內(nèi)存調(diào)小一點(diǎn)。
-Xmx5M :最大內(nèi)存值5M -Xms5M:初始內(nèi)存大小5M

測試代碼
public?static?void?main(String[]?args)?{
????ExecutorService?service?=?Executors.newSingleThreadExecutor();
????while?(true){
????????service.execute(()?->?{
????????????System.out.println("我是一個(gè)任務(wù),運(yùn)行時(shí)間:"+System.currentTimeMillis()+"\n");
????????});
????}
}
測試結(jié)果
任務(wù)跑了1分鐘左右,就發(fā)生了OOM異常

三、總結(jié)
阿里巴巴開發(fā)手冊(cè)為什么禁止使用 Executors 去創(chuàng)建線程池,原因就是 newFixedThreadPool() 和 newSingleThreadExecutor()兩個(gè)方法允許請(qǐng)求的最大隊(duì)列長度是 Integer.MAX_VALUE ,可能會(huì)出現(xiàn)任務(wù)堆積,出現(xiàn)OOM。newCachedThreadPool()允許創(chuàng)建的線程數(shù)量為 Integer.MAX_VALUE,可能會(huì)創(chuàng)建大量的線程,導(dǎo)致發(fā)生OOM。它建議使用ThreadPoolExecutor方式去創(chuàng)建線程池,通過上面的分析我們也知道了其實(shí)Executors 三種創(chuàng)建線程池的方式最終就是通過ThreadPoolExecutor來創(chuàng)建的,只不過有些參數(shù)我們無法控制,如果通過ThreadPoolExecutor的構(gòu)造器去創(chuàng)建,我們就可以根據(jù)實(shí)際需求控制線程池需要的任何參數(shù),避免發(fā)生OOM異常。
往期精選:
Redis 為什么默認(rèn) 16 個(gè)數(shù)據(jù)庫?
在這里獲得的不僅僅是技術(shù)!

喜歡就給個(gè)“在看”
