文章目錄
1 爲什麼要使用線程池?
a.假如線程創建的時間是time1,線程執行的時間是time2,線程銷燬的時間呢是time3,往往time1+time3>time2,所以頻繁的創建線程,會消耗額外的時間
b.如果等到有任務來了,在去創建線程的話效率就會比較低,如不把線程放在某個地方,任務來了,直接把線程拿過來用比較好
c.線程池可以管理控制線程,線程是稀缺資源,如果不停地創建線程會消耗大量的系統資源,還會降低系統的穩定性。使用線程池,可以進行統一分配,方便調優和監控
d.線程可以提供隊列,存放緩衝等待執行的任務
2 效果對比
- 下面我們來模擬一下 100個用戶同時訪問web端 執行一個操作
普通線程啓動方式
/**
* 普通處理線程的方法
*/
@Test
public void oldMethod() throws InterruptedException {
//書寫一個循環 模擬100個用戶同時訪問
for (int request = 0; request< 100;request ++){
//開啓一個線程
new Thread(()->{
System.out.println("開始執行...");
/**
* 睡10秒
*/
try {
Thread.sleep(1000L * 30);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("結束執行...");
}).start();
}
//爲了方便測試 我們讓主線程睡一會
Thread.sleep(1000 * 1000);
}
這時我們看下下面的圖片的,一共有100個線程在執行,如果要是10000個用戶來訪問該程序,系統不得直接給幹崩潰了!所以我們就得使用下面的線程池
利用線程池啓動線程方式
@Test
public void newMethod() throws InterruptedException {
/**
* 開啓一個線程池 默認線程是10個
* 使用默認線程工廠
* 拒絕策略爲CallerRunsPolicy策略,讓後面的線程先等待
*/
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10, 1000L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(10),Executors.defaultThreadFactory(),new ThreadPoolExecutor.CallerRunsPolicy());
//書寫一個循環 模擬100個用戶同時訪問
for (int request = 0; request< 100;request ++){
//開啓一個線程
threadPoolExecutor.execute(() ->{
System.out.println("開始執行...");
/**
* 睡10秒
*/
try {
Thread.sleep(1000L * 30);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("結束執行...");
});
}
//爲了方便測試 我們讓主線程睡一會
Thread.sleep(1000 * 1000);
}
我們可以看到這時候只有10個線程在跑,剩下的也都在等待該這是個線程跑完,纔會有新的線程執行
3 線程池簡介
- 降低資源消耗
- 提高響應速度
- 提高線程的可管理性
4 簡單線程池的設計
簡單線程池設計思路
要有阻塞隊列丶執行器丶線程池子(存放線程)
- 設計過程中要思考的問題
- 初始創建多少線程?
- 沒有可用線程了怎麼辦?
- 緩衝數組要多長?
- 緩衝數組滿了怎麼辦?
5 線程池的核心參數
- corePoolSize 核心池大小
- maximumPoolSize 池中允許的最大線程,這個參數表示了線程池中最多的線程數量
- keepAliveTime 當線程大於corePoolSize時,終止前多餘的空閒線程等待新任務的最長時間
- unit 上一個參數的時間單位
- workQueue 存儲還沒來的及執行的任務
- threadFactory 執行程序創建新線程時使用的工廠
- handler 由於超出線程範圍和隊列容量而使執行被阻塞時使用的處理程序
6 線程池的處理流程
從下面這張圖我們可以看出:
1.提交任務
2.判斷線程數是否達到最大(是則進入判斷任務隊列)否就提交任務
3.判斷任務隊列是否已滿(是則進入判斷最大線程數)否就將任務加在任務隊列
4.判斷線程達到最大線程數(是則進入飽和策略)否則創建非核心線程執行任務
5.執行飽和策略,根據飽和策略的不同,程序作出不同的響應
7 線程池可選擇的阻塞隊列
- 無界隊列(常用的爲無界的LinkedBlockingQueue)
可以無限創建線程(有風險) - 有界隊列
常用的有兩類,一類是遵循FIFO原則的隊列如ArrayBlockingQueue,另一類是優先級隊列如PriorityBlockingQueue。PriorityBlockingQueue中的優先級由任務的Comparator決定。
使用有界隊列時隊列大小需和線程池大小互相配合,線程池較小有界隊列較大時可減少內存消耗,降低cpu使用率和上下文切換,但是可能會限制系統吞吐量。
在我們的修復方案中,選擇的就是這個類型的隊列,雖然會有部分任務被丟失,但是我們線上是排序日誌蒐集任務,所以對部分對丟失是可以容忍的。
- 同步移交隊列
如果不希望任務在隊列中等待而是希望將任務直接移交給工作線程,可使用SynchronousQueue作爲等待隊列。SynchronousQueue不是一個真正的隊列,而是一種線程之間移交的機制。要將一個元素放入SynchronousQueue中,必須有另一個線程正在等待接收這個元素。只有在使用無界線程池或者有飽和策略時才建議使用該隊列。
8 線程池可選擇的飽和策略
AbortPolicy終止策略(默認)
DiscardPolicy拋棄策略
DiscardOldestPolicy拋棄舊任務策略
CallerRunsPolicy調用者運行策略
9 線程池的執行示意圖
10 常用線程池
newCachedThreadPool線程數量無限線程池
newFixedThreadPool線程數量固定線程池
newSingleThreadExecutor單一線程線程池
11 線程池的狀態
五種狀態
- RUNNING
- SHUTDOWN
- STOP
- TIDYING
- TERMINATED