【強制】線程池不允許使用 Executors 去創建,而是通過 ThreadPoolExecutor 的方式,這樣的處理方式讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險。
說明: Executors 返回的線程池對象的弊端 如下
1 FixedThreadPool 和 SingleThread Pool
允許的請求隊列長度爲 Integer.MAX_VALUE,可 能會堆積大量的請求,從而導致 OOM。
2 CachedThreadPool 和 ScheduledThreadPool
允許的創建線程數量爲 Integer.MAX_VALUE 可能會創建大量的線程,從而導致 OOM。
1、線程池解決了哪些問題?
(1)複用已有的線程資源;
(2)可以對任務限流,控制線程數量,使用不同的任務拒絕策略;
(3)降低頻繁的創建和銷燬線程;
2、ThreadPoolExecutor構造函數
取自全參數的構造函數代碼。
/*
*使用給定的初始名稱創建一個新的{@code ThreadPoolExecutor}參數。
*
* @param corePoolSize保留在池中的線程數,即使如果它們空閒,除非設置了{@code allowCoreThreadTimeOut}
* @param maximumPoolSize在允許的最大線程數池
* @param keepAliveTime當線程數大於核心,這是多餘的空閒線程的最長時間將終止之前等待新任務。
* @param unit {@code keepAliveTime}參數的時間單位
* @param work將隊列用於保存任務之前將其暫存已執行。此隊列將僅容納{@code Runnable}
* 由{@code execute}方法提交的任務。
* @param threadFactory執行程序時要使用的工廠創建一個新線程
* @param handler阻止執行時使用的處理程序因爲達到了線程界限和隊列容量
* @throws IllegalArgumentException如果以下條件之一成立:<br>
* {@code corePoolSize <0} <br>
* {@code keepAliveTime <0} <br>
* {@code maximumPoolSize <= 0} <br>
* {@code maximumPoolSize <corePoolSize}
*如果{@code workQueue},則拋出NullPointerException
*或{@code threadFactory}或{@code handler}爲空
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
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;
}
3、 線程池執行流程圖
4、參數解釋
參數 | 類型 | 解釋 |
---|---|---|
corePoolSize | int | 核心線程數,如果allowCoreThreadTimeOut爲false,即使線程處於空閒狀態,也會保留在池中。如果爲true,則根據keepAliveTime剔除。 |
maximumPoolSize | int | 最大線程數 |
keepAliveTime | long | 如果線程池當前擁有超過corePoolSize的線程數,那麼多餘的線程在空閒時間超過keepAliveTime時會被終止 。 |
unit | TimeUnit | 時間單位 |
workQueue | BlockingQueue | 線程等待隊列 |
threadFactory | ThreadFactory | 執行程序時要使用工廠創建一個新線程 |
handler | RejectedExecutionHandler | 拒絕策略,當達到了線程界面和隊列容量,要使用的處理程序 |
5、部分參數詳細解釋
5.1、workQueue 等待隊列
用於存放提交的任務,隊列的實際容量與線程池穩定性相關聯。
如果當前線程池任務線程數量小於核心線程池數量,執行器總是優先創建一個任務線程,而不是從線程池中取一個空閒線程。
如果當前線程池任務線程數量大於核心線程池數量,執行器總是優先從線程隊列中取一個空閒線程,而不是創建一個任務線程。
如果當前線程池任務線程數量大於核心線程池數量,且池中無空閒任務線程,將會創建一個任務線程,直到超出maximumPoolSize,如果超出maximumPoolSize,則任務將會被拒絕。
主要有三種隊列策略:
(1)Direct handoffs 直接握手隊列
Direct handoffs的默認選擇是 SynchronousQueue,它將任務直接交給線程而不是自己保留。如果沒有線程能立即運行它,將構建新的線程。Direct handoffs通常需要無限制的maximumPoolSizes來避免拒絕新提交的任務。
注意:當任務持續以平均提交速度大餘平均處理速度時,會導致線程數量會無限增長問題。
(2)Unbounded queues 無界隊列(例如未定義容量的LinkedBlockingQueue)
當所有corePoolSize線程繁忙時,使用無界隊列將導致新任務在隊列中等待,從而導致maximumPoolSize的值沒有任何作用,從而導致OOM。
(3)Bounded queues 有界隊列(例如ArrayBlockingQueue)
一個有界的隊列和有限的maximumPoolSizes配置有助於防止資源耗盡,但是難以控制。隊列大小和maximumPoolSizes需要相互權衡。
5.2、threadFactory 線程工廠
默認爲Executors.defaultThreadFactory()。
通過提供不同的ThreadFactory,您可以更改線程的名稱,線程組,優先級,守護線程狀態等。
5.3、RejectedExecutionHandler 拒絕策略
拒絕任務有兩種情況:
(1) 線程池已經被關閉;
(2)任務隊列已滿且maximumPoolSizes已滿;
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
預定義了四種處理策略:
AbortPolicy:默認策略,拋出RejectedExecutionException運行時異常;
CallerRunsPolicy:調用當前線程池的所在的線程去執行被拒絕的任務;
DiscardPolicy:直接丟棄新提交的任務;
DiscardOldestPolicy:如果執行器沒有關閉,隊列頭的任務將會被丟棄,然後執行器重新嘗試執行任務(如果失敗,則重複這一過程);
我們可以自己定義RejectedExecutionHandler,以適應特殊的容量和隊列策略場景中。
6、Executors預定義線程池
6.1 newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
隊列:workQueue爲LinkedBlockingQueue(無界阻塞隊列),隊列最大值爲Integer.MAX_VALUE。如果任務提交速度持續大餘任務處理速度,會造成隊列大量阻塞。因爲隊列很大,很有可能在拒絕策略前,內存溢出。
適用場景:可用於Web服務瞬時削峯,但需注意長時間持續高峯情況造成的隊列阻塞。
6.2 newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
隊列:workQueue 爲 SynchronousQueue 同步隊列,這個隊列類似於一個接力棒,入隊出隊必須同時傳遞,因爲CachedThreadPool線程創建無限制,不會有隊列等待,所以使用SynchronousQueue;
適用場景:快速處理大量耗時較短的任務,如Netty的NIO接受請求時,可使用CachedThreadPool。
6.3 newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
6.4 newScheduledThreadPool
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
ScheduledThreadPoolExecutor可以用來在給定延時後執行異步任務或者週期性執行任務,相對於任務調度的Timer來說,其功能更加強大,Timer只能使用一個後臺線程執行任務,而ScheduledThreadPoolExecutor則可以通過構造函數來指定後臺線程的個數。
6.5 newWorkStealingPool
public static ExecutorService newWorkStealingPool(int parallelism) {
return new ForkJoinPool
(parallelism,
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
null, true);
}
創建一個線程池,該線程池維護足夠的線程以支持給定的並行級別,並且可以使用多個隊列來減少爭用。並行級別對應於活動參與或可用參與任務處理的最大線程數。實際的線程數可能會動態增長和收縮。工作竊取池無法保證提交的任務的執行順序。