前言
上文我們介紹了JDK中的線程池框架Executor
谜嫉。我們知道,只要需要?jiǎng)?chuàng)建線程的情況下范抓,即使是在單線程模式下骄恶,我們也要盡量使用Executor
。即:
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(1);
//此處不該利用Executors工具類來初始化線程池
但是匕垫,在《阿里巴巴Java開發(fā)手冊(cè)》中有一條
【強(qiáng)制】線程池不允許使用 Executors 去創(chuàng)建僧鲁,而是通過 ThreadPoolExecutor 的方式,這樣的處理方式讓寫的同學(xué)更加明確線程池的運(yùn)行規(guī)則象泵,規(guī)避資源耗盡的風(fēng)險(xiǎn)寞秃。
Executors 返回的線程池對(duì)象的弊端如下:
FixedThreadPool 和 SingleThreadPool : 允許的請(qǐng)求隊(duì)列長(zhǎng)度為 Integer.MAX_VALUE,可能會(huì)堆積大量的請(qǐng)求偶惠,從而導(dǎo)致 OOM春寿。
CachedThreadPool 和 ScheduledThreadPool : 允許的創(chuàng)建線程數(shù)量為 Integer.MAX_VALUE,可能會(huì)創(chuàng)建大量的線程忽孽,從而導(dǎo)致 OOM绑改。
可以看到谢床,這是一個(gè)強(qiáng)制性的規(guī)則,并且是不允許使用Executors
來創(chuàng)建厘线,建議使用ThreadPoolExecutor
來創(chuàng)建線程池识腿,那我們先來回顧一下Executors
和ThreadPoolExecutor
。
我們可以看到ThreadPoolExecutor
已經(jīng)是Executor
的具體實(shí)現(xiàn)了造壮,而且具有較多可配參數(shù)(可配參數(shù)見下方渡讼,可僅了解,用到時(shí)再進(jìn)行詳細(xì)查詢)耳璧。Executors
是一個(gè)創(chuàng)建線程池的工具類成箫,查看其源碼的話也會(huì)發(fā)現(xiàn)這幾種創(chuàng)建線程池的方法也都是通過調(diào)用ThreadPoolExecutor來實(shí)現(xiàn)的。
ThreadPoolExecutor一共有四個(gè)構(gòu)造函數(shù)旨枯,七個(gè)可配參數(shù)蹬昌,分別是
- corePoolSize: 線程池中保持存活線程的數(shù)量。
- maximumPoolSize: 線程池中允許線程數(shù)量的最大值
- keepAliveTime: 表示線程沒有任務(wù)執(zhí)行時(shí)最多保持多久時(shí)間會(huì)終止
- unit: 參數(shù)keepAliveTime的時(shí)間單位
- workQueue: 一個(gè)阻塞隊(duì)列攀隔,用來存儲(chǔ)等待執(zhí)行的任務(wù)
- threadFactory: 線程工廠凳厢,主要用來創(chuàng)建線程
- handler:表示當(dāng)拒絕處理任務(wù)時(shí)的策略
分析
那么Executors到底會(huì)導(dǎo)致什么問題,才會(huì)讓開發(fā)手冊(cè)中直接被定義為不允許了呢竞慢。首先就是一個(gè)血淋淋的教訓(xùn),直接導(dǎo)致線上服務(wù)不可用治泥,已經(jīng)可以算是事故了筹煮。
實(shí)驗(yàn)
我們也可以現(xiàn)在我們本地進(jìn)行一下小實(shí)驗(yàn):
public class ExecutorsTesting {
private static ExecutorService executor = Executors.newFixedThreadPool(15);
public static void main(String[] args) {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
executor.execute(new SubThread());
}
}
}
class SubThread implements Runnable {
@Override
public void run() {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
//do nothing
}
}
}
運(yùn)行時(shí)指定JVM參數(shù):-Xmx8m -Xms8m
,大概幾秒鐘之后居夹,會(huì)報(bào)出OOM錯(cuò)誤:
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
at com.kaikeba.mybatis.ExecutorsTesting.main(ExecutorsTesting.java:10)
//報(bào)錯(cuò)行數(shù)為上述代碼中的executor.execute(new SubThread());
那么為什么會(huì)報(bào)出這個(gè)錯(cuò)誤呢败潦。
源碼分析
我們先來看一下Executors
中的FixedThreadPool
是如何構(gòu)造的。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
可以看到對(duì)于存儲(chǔ)等待執(zhí)行的任務(wù)准脂,FixedThreadPool
是通過LinkedBlockingQueue
來實(shí)現(xiàn)的劫扒。而我們知道LinkedBlockingQueue
是一個(gè)鏈表實(shí)現(xiàn)的阻塞隊(duì)列,而如果不設(shè)置其容量的話狸膏,將會(huì)是一個(gè)無邊界的阻塞隊(duì)列沟饥,最大長(zhǎng)度為Integer.MAX_VALUE
。由于Executors
中并未設(shè)置容量湾戳,所以應(yīng)用可以不斷向隊(duì)列中添加任務(wù)贤旷,導(dǎo)致OOM錯(cuò)誤。
上面提到的問題主要體現(xiàn)在newFixedThreadPool
和newSingleThreadExecutor
兩個(gè)工廠方法上砾脑,并不是說newCachedThreadPool
和newScheduledThreadPool
這兩個(gè)方法就安全了幼驶,這兩種方式創(chuàng)建的最大線程數(shù)可能是Integer.MAX_VALUE
,而創(chuàng)建這么多線程韧衣,必然就有可能導(dǎo)致OOM盅藻。
如何該利用ThreadPoolExecutor來創(chuàng)建線程池呢购桑?
我們其實(shí)可以看到Executors
中的newFixedThreadPool
其實(shí)也是調(diào)用ThreadPoolExecutor
來實(shí)現(xiàn)的。正如手冊(cè)中所說氏淑,當(dāng)我們不用Executors
默認(rèn)創(chuàng)建線程池的方法勃蜘,而直接自己手動(dòng)去調(diào)用ThreadPoolExecutor
,可以讓寫的同學(xué)更加明確線程池的運(yùn)行規(guī)則夸政,規(guī)避資源耗盡的風(fēng)險(xiǎn)元旬。比如我們?cè)?code>Executors.newFixedThreadPool基礎(chǔ)上給LinkedBlockingQueue
加一個(gè)容量,當(dāng)隊(duì)列已經(jīng)滿了守问,而仍需要添加新的請(qǐng)求會(huì)拋出相應(yīng)異常匀归,我們可以根據(jù)異常做相應(yīng)處理。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(10)); //添加容量大小
}
除了自己定義ThreadPoolExecutor
外耗帕。還可以利用其它開源類庫(kù)穆端,如apache和guava等,可以有更多個(gè)性化配置仿便。
參考文章:
https://www.hollischuang.com/archives/2888
https://blog.51cto.com/zero01/2306857
本文由博客一文多發(fā)平臺(tái) OpenWrite 發(fā)布体啰!
文章首發(fā):https://zhuanlan.zhihu.com/lovebell
個(gè)人公眾號(hào):技術(shù)Go
您的點(diǎn)贊與支持是作者持續(xù)更新的最大動(dòng)力!