最近在看阿里Java編程規範,有一條引起了我的注意:
【強制】線程池不允許使用
Executors
去創建,而是通過ThreadPoolExecutor
的方式,這樣的處理方式讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險。
說明:Executors
返回的線程池對象的弊端如下:
FixedThreadPool
和SingleThreadPool
: 允許的請求隊列長度爲Integer.MAX_VALUE
,可能會堆積大量的請求,從而導致 OOM。CachedThreadPool
和ScheduledThreadPool
: 允許的創建線程數量爲Integer.MAX_VALUE
,可能會創建大量的線程,從而導致 OOM。
平時沒怎麼注意這一點,一般用的多的還是FixedThreadPool
和 SingleThreadPool
。那怎麼理解這條規範呢?首先,ThreadPoolExecutor
是更底層的類,直接用它的構造方法,可以讓我們對創建的線程池有更強的控制。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
我們來看下ThreadPoolExecutor
的參數設定:
corePoolSize
:核心線程數(就算有空閒線程,也不會低於這個數)maximumPoolSize
:最大線程數keepAliveTime
:最大存活時間(超過corePoolSize
數量的線程空閒下最大存活時間)unit
:時間單位workQueue
:工作隊列(核心線程滿後,任務會進入這裏)handler
:任務滿負荷(超出最大線程數和工作隊列上限之和)後的處理策略
其中處理策略有四種:
AbortPolicy
:默認策略,在需要拒絕任務時拋出RejectedExecutionException
CallerRunsPolicy
:直接在 execute 方法的調用線程中運行被拒絕的任務,如果線程池已經關閉,任務將被丟棄DiscardPolicy
:直接丟棄任務DiscardOldestPolicy
:丟棄隊列中等待時間最長的任務,並執行當前提交的任務,如果線程池已經關閉,任務將被丟棄
除此之外我們也可以自定義處理策略。
作爲對比,我們看下FixedThreadPool
的實現:
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
可見它實際上就是個簡化版的ThreadPoolExecutor
,通過固定線程數,然後指定工作隊列長度爲無限(不傳長度代表容量無限)。這樣確實可能會造成任務無限堆積,引起OOM的問題。所以阿里的規範建議我們直接使用更底層的ThreadPoolExecutor
,把處理策略之類的都提前設定好。
不過規範也不是死的,在實際情況中也無法避免使用FixedThreadPool
等更高層的實現。一方面是因爲使用起來更加方便,另一方面也是有時候使用ThreadPoolExecutor
並沒有明顯的好處。例如:線程池中分配的任務都是關鍵任務,無法被丟棄,然後因爲性能考慮也無法被execute線程同步執行,這樣就很難定義一個合適的處理策略,不如直接使用FixedThreadPool
。就算有OOM的風險,那也只能說在多種風險權衡下做的一個評估和取捨。