ThreadPoolExecutor 引數介紹 【轉載】
ThreadPoolExecutor 引數介紹
ThreadPoolExecutor 最多可以設定 7 個引數,如下程式碼所示:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
// 省略...
}
7 個引數代表的含義如下:
引數 1:corePoolSize
核心執行緒數,執行緒池中始終存活的執行緒數。
引數 2:maximumPoolSize
最大執行緒數,執行緒池中允許的最大執行緒數,當執行緒池的任務佇列滿了之後可以建立的最大執行緒數。
引數 3:keepAliveTime
最大執行緒數可以存活的時間,當執行緒中沒有任務執行時,最大執行緒就會銷燬一部分,最終保持核心執行緒數量的執行緒。
引數 4:unit:
單位是和引數 3 存活時間配合使用的,合在一起用於設定執行緒的存活時間 ,引數 keepAliveTime 的時間單位有以下 7 種可選:
- TimeUnit.DAYS:天
- TimeUnit.HOURS:小時
- TimeUnit.MINUTES:分
- TimeUnit.SECONDS:秒
- TimeUnit.MILLISECONDS:毫秒
- TimeUnit.MICROSECONDS:微妙
- TimeUnit.NANOSECONDS:納秒
引數 5:workQueue
一個阻塞佇列,用來儲存執行緒池等待執行的任務,均為執行緒安全,它包含以下 7 種類型:
- ArrayBlockingQueue:一個由陣列結構組成的有界阻塞佇列。
- LinkedBlockingQueue:一個由連結串列結構組成的有界阻塞佇列。
- SynchronousQueue:一個不儲存元素的阻塞佇列,即直接提交給執行緒不保持它們。
- PriorityBlockingQueue:一個支援優先順序排序的無界阻塞佇列。
- DelayQueue:一個使用優先順序佇列實現的無界阻塞佇列,只有在延遲期滿時才能從中提取元素。
- LinkedTransferQueue:一個由連結串列結構組成的無界阻塞佇列。與SynchronousQueue類似,還含有非阻塞方法。
- LinkedBlockingDeque:一個由連結串列結構組成的雙向阻塞佇列。
較常用的是LinkedBlockingQueue
和Synchronous
,執行緒池的排隊策略與BlockingQueue
有關。
引數 6:threadFactory
執行緒工廠,主要用來建立執行緒,預設為正常優先順序、非守護執行緒。
引數 7:handler
拒絕策略,拒絕處理任務時的策略,系統提供了 4 種可選:
- AbortPolicy:拒絕並丟擲異常。
- CallerRunsPolicy:使用當前呼叫的執行緒來執行此任務。
- DiscardOldestPolicy:拋棄佇列頭部(最舊)的一個任務,並執行當前任務。
- DiscardPolicy:忽略並拋棄當前任務。
預設策略為AbortPolicy
。
執行緒池的執行流程
ThreadPoolExecutor 關鍵節點的執行流程如下:
- 當執行緒數小於核心執行緒數時,建立執行緒。
- 當執行緒數大於等於核心執行緒數,且任務佇列未滿時,將任務放入任務佇列。
- 當執行緒數大於等於核心執行緒數,且任務佇列已滿:若執行緒數小於最大執行緒數,建立執行緒;若執行緒數等於最大執行緒數,丟擲異常,拒絕任務。
執行緒池的執行流程如下圖所示:
執行緒拒絕策略
我們來演示一下 ThreadPoolExecutor 的拒絕策略的觸發,我們使用DiscardPolicy
的拒絕策略,它會忽略並拋棄當前任務的策略,實現程式碼如下:
public static void main(String[] args) {
// 任務的具體方法
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("當前任務被執行,執行時間:" + new Date() +
" 執行執行緒:" + Thread.currentThread().getName());
try {
// 等待 1s
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
// 建立執行緒,執行緒的任務佇列的長度為 1
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 1,
100, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1),
new ThreadPoolExecutor.DiscardPolicy());
// 新增並執行 4 個任務
threadPool.execute(runnable);
threadPool.execute(runnable);
threadPool.execute(runnable);
threadPool.execute(runnable);
}
我們建立了一個核心執行緒數和最大執行緒數都為 1 的執行緒池,並且給執行緒池的任務佇列設定為 1,這樣當我們有 2 個以上的任務時就會觸發拒絕策略,執行的結果如下圖所示:
從上述結果可以看出只有兩個任務被正確執行了,其他多餘的任務就被捨棄並忽略了。其他拒絕策略的使用類似,這裡就不一一贅述了。
自定義拒絕策略
除了 Java 自身提供的 4 種拒絕策略之外,我們也可以自定義拒絕策略,示例程式碼如下:
public static void main(String[] args) {
// 任務的具體方法
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("當前任務被執行,執行時間:" + new Date() +
" 執行執行緒:" + Thread.currentThread().getName());
try {
// 等待 1s
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
// 建立執行緒,執行緒的任務佇列的長度為 1
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 1,
100, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1),
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
// 執行自定義拒絕策略的相關操作
System.out.println("我是自定義拒絕策略~");
}
});
// 新增並執行 4 個任務
threadPool.execute(runnable);
threadPool.execute(runnable);
threadPool.execute(runnable);
threadPool.execute(runnable);
}
程式的執行結果如下:
究竟選用哪種執行緒池?
經過以上的學習我們對整個執行緒池也有了一定的認識了,那究竟該如何選擇執行緒池呢?
我們來看下阿里巴巴《Java開發手冊》給我們的答案:
【強制要求】執行緒池不允許使用 Executors 去建立,而是通過 ThreadPoolExecutor 的方式,這樣的處理方式讓寫的同學更加明確執行緒池的執行規則,規避資源耗盡的風險。
說明:Executors 返回的執行緒池物件的弊端如下:
1) FixedThreadPool 和 SingleThreadPool:允許的請求佇列長度為 Integer.MAX_VALUE,可能會堆積大量的請求,從而導致 OOM。
2)CachedThreadPool:允許的建立執行緒數量為 Integer.MAX_VALUE,可能會建立大量的執行緒,從而導致 OOM。
所以綜上情況所述,我們推薦使用ThreadPoolExecutor
的方式進行執行緒池的建立,因為這種建立方式更可控,並且更加明確了執行緒池的執行規則,可以規避一些未知的風險。
總結
本文我們介紹了執行緒池的 7 種建立方式,其中最推薦使用的是ThreadPoolExecutor
的方式進行執行緒池的建立,ThreadPoolExecutor
最多可以設定 7 個引數,當然設定 5 個引數也可以正常使用,ThreadPoolExecutor
當任務過多(處理不過來)時提供了 4 種拒絕策略,當然我們也可以自定義拒絕策略,希望本文的內容能幫助到你。原創不易,覺得不錯就點個贊再走吧!
參考 & 鳴謝
https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html