快速到達看這裏-->
線程池的好處
- 加快響應速度
- 合理利用CPU和內存
- 統一管理資源
線程池適用場合
- 服務器接收大量請求時,使用線程池可以大大減少線程的創建和銷燬的次數,提高服務器的工作效率
- 開發中,如果需要創建5個以上的線程。那麼就可以使用線程池來管理
線程池的創建
線程池的構造函數的參數
new ThreadPoolExecutor
corePoolSize
指核心線程數:線程池在初始化完畢後,默認情況下,線程池沒有任何線程,線程池會等到任務來的時候創建新線程去執行任務- 線程池可能會在覈心線程數的基礎上增加一些線程,但是育一個上限,就是
maximumPoolSize
- 如果線程池當前線程數多餘corePoolSize,多餘的線程空閒時間超過
keepAliveTime
,這部分線程就會被終止 - 新的線程是由
ThreadFactory
創建的,通常使用默認的就夠用了- 默認的使用Executor.defaultThreadFactory(),創建出來的線程都在同一個線程組,擁有同樣的優先級,且都不是守護線程
- 自己指定ThreadFactory,就可以改變線程名,線程組,優先級,是否守護線程等
- 常用的
workQueue
- 直接交換:SynchronousQueue 本身內部沒有容量
- 無界隊列:LinkedBlockingQueue 隊列放不滿
- 有界隊列:ArrayBlockingQueue 可以自己設置大小,maximumPoolSize纔有意義
添加線程規則
- 如果線程數小於corePoolSize,即使其他工作線程處於空閒狀態,也會創建一個新線程來運行任務
- 如果線程數等於或大於corePoolSize但少於maximumPoolSize,將任務放入隊列
- 如果隊列滿了,並且線程數小於maximumPoolSize,則創建一個新線程來運行任務
- 如果隊列已滿,並且線程數大於或等於maximumPoolSize,則拒絕該任務
舉個例子:
- 線程池:核心池大小5,最大池大小10,隊列100
- 前5個請求會在覈心池創建5個線程,然後進來的任務會添加到隊列中,直到達到100。當隊列滿了後,再創建新線程,當總線程數達到maximumPoolSize = 10 後,再來任務請求,就直接拒絕
增減線程的特點
- 通過設置maximumPoolSize 和 corePoolSize相同,就可以創建固定大小的線程池。
- 線程池希望保持較少的線程數,並且只有在負載變得很大時才增加它
- 通過設置maximumPoolSize 爲非常大的值,可以允許線程池容納任意數量的併發任務
- 只有隊列填滿時才創建多餘corePoolSize的線程,所以使用的是無邊界隊列(如LinkedBlockingQueue),那麼線程數就不會超過corePoolSize
線程池應該手動創建還是自動創建
-
手動創建更好,可以更明確線程池的運行規則,避免資源浪費
-
正確創建線程池的方法
- 根據不同的業務場景,自己設置線程池參數
- 線程池中線程數目設定
- CPU密集型(加密,計算hash等):最佳線程池爲CPU核心數的1-2倍左右
- 耗時IO型(讀寫數據庫,文件,網絡讀寫等):最佳線程數一般會大於CPU核心數很多倍
線程數 = CPU核心數(1+平均等待時間/平均工作時間)
幾種常見線程池
- 幾種常見自動創建的線程池
- newFixedThreadPool
任務隊列採用的是LinkedBlockingQueue,沒有容量上限 ,請求越來多導致無法及時處理時就會造成請求堆積,堆積足夠多可能造成OOM - newSingleThreadExecutor
任務隊列採用的是LinkedBlockingQueue,線程數爲1,同樣可能造成OOM - newCachedThreadPool
將maximumPoolSize 被設置成爲Integer.MAX_VALUE,雖然能做到線程超時回收,但是當創建非常多的時候可能造成OOM - ScheduledThreadPool
按一定時間間隔週期性的運行線程,不使用大多數場景
- newFixedThreadPool
如何正確停止一個線程池
- shutdown()
調用shutdown()方法,進入shutdown狀態,線程池開始停止,新的任務請求全部被拒絕,等待已經在運行的任務和任務隊列中的任務全部執行完成後徹底停止 - isShutdown()
調用isShutdown()方法可以判斷線程池是否開始停止
//預期爲false
System.out.println("isShutdown1:"+executorService.isShutdown());
executorService.shutdown();
//預期爲true
System.out.println("isShutdown2:"+executorService.isShutdown());
//shutdow之後的請求就會被拒絕
executorService.execute(new ShutDownTask());
- isTerminated()
調用 isTerminated()方法可以判斷線程池是否徹底停止
executorService.shutdown();
//預期爲false
System.out.println("isTerminated1:"+executorService.isTerminated());
Thread.sleep(10000);
//等待十秒,再看是否徹底停止,預期爲true
System.out.println("isTerminated2:"+executorService.isTerminated());
- awaitTerminated()
檢測一段時間內線程池是否徹底停止
boolean b = executorService.awaitTermination(3L, TimeUnit.SECONDS);//3秒內是否徹底停止
System.out.println(b);
- shutdownNow()
對正在運行的線程調用interrupt通知中斷,線程池隊列中等待的任務會作爲返回值返回,方便將未執行的任務加入到新的線程池或者寫入日誌
List<Runnable> runnables = executorService.shutdownNow();
線程池停止的完整代碼如下:
/**
* 〈關閉線程池〉
*
* @author Chkl
* @create 2020/3/11
* @since 1.0.0
*/
public class ShutDown {
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService =
Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i++) {
executorService.execute(new ShutDownTask());
}
Thread.sleep(1500);
List<Runnable> runnables = executorService.shutdownNow();
// //預期爲false
// System.out.println("isShutdown1:"+executorService.isShutdown());
// executorService.shutdown();
// //預期爲true
// System.out.println("isShutdown2:"+executorService.isShutdown());
// //shutdow之後的請求就會被拒絕
// //executorService.execute(new ShutDownTask());
//
// //預期爲false
// System.out.println("isTerminated1:"+executorService.isTerminated());
// Thread.sleep(10000);
// //等待十秒,再看是否徹底停止,預期爲true
// System.out.println("isTerminated2:"+executorService.isTerminated());
}
static class ShutDownTask implements Runnable {
@Override
public void run() {
try {
Thread.sleep(500);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName()+"被中斷了");
}
}
}
}
任務太多,怎麼拒絕
拒絕時機
- 當Executor關閉時,提交新任務會被拒絕
- 當Executor的最大線程和最大容量使用有限邊界並且已經飽和時
拒絕策略
- AbortPolicy
直接拋出一個異常 - DiscardPolicy
偷偷地丟棄任務,不通知 - DiscaedOldestPolicy
偷偷丟棄最老的(隊列中存在時間最久的)一個,不通知 - CallerRunsPolicy
讓提交任務的線程去幫忙執行
本文參考了:《玩轉Java併發工具》
更多Java面試複習筆記和總結可訪問我的面試複習專欄《Java面試複習筆記》,或者訪問我另一篇博客《Java面試核心知識點彙總》查看目錄和直達鏈接