執行緒池的基本原理,看完就懂了
原文地址: http://blog.jboost.cn/2019/07/05/threadpool.html
本文內容是基於研發部門內部的分享整理,記錄下來供學習或回顧。
1. 為什麼要用執行緒池
-
降低資源消耗。通過重複利用已建立的執行緒降低執行緒建立、銷燬執行緒造成的消耗。
-
提高響應速度。當任務到達時,任務可以不需要等到執行緒建立就能立即執行。
-
提高執行緒的可管理性。執行緒是稀缺資源,如果無限制的建立,不僅會消耗系統資源,還會降低系統的穩定性,使用執行緒池可以進行統一的分配、調優和監控
2. ThreadPoolExecutor執行緒池類引數詳解
引數 | 說明 |
---|---|
corePoolSize | 核心執行緒數量,執行緒池維護執行緒的最少數量 |
maximumPoolSize | 執行緒池維護執行緒的最大數量 |
keepAliveTime | 執行緒池除核心執行緒外的其他執行緒的最長空閒時間,超過該時間的空閒執行緒會被銷燬 |
unit | keepAliveTime的單位,TimeUnit中的幾個靜態屬性:NANOSECONDS、MICROSECONDS、MILLISECONDS、SECONDS |
workQueue | 執行緒池所使用的任務緩衝佇列 |
threadFactory | 執行緒工廠,用於建立執行緒,一般用預設的即可 |
handler | 執行緒池對拒絕任務的處理策略 |
當執行緒池任務處理不過來的時候(什麼時候認為處理不過來後面描述),可以通過handler指定的策略進行處理,ThreadPoolExecutor提供了四種策略:
- ThreadPoolExecutor.AbortPolicy:丟棄任務並丟擲RejectedExecutionException異常;也是預設的處理方式。
- ThreadPoolExecutor.DiscardPolicy:丟棄任務,但是不丟擲異常。
- ThreadPoolExecutor.DiscardOldestPolicy:丟棄佇列最前面的任務,然後重新嘗試執行任務(重複此過程)
- ThreadPoolExecutor.CallerRunsPolicy:由呼叫執行緒處理該任務
可以通過實現RejectedExecutionHandler介面自定義處理方式。
3. 執行緒池任務執行
3.1. 新增執行任務
- submit() 該方法返回一個Future物件,可執行帶返回值的執行緒;或者執行想隨時可以取消的執行緒。Future物件的get()方法獲取返回值。Future物件的cancel(true/false)取消任務,未開始或已完成返回false,引數表示是否中斷執行中的執行緒
- execute() 沒有返回值。
3.2. 執行緒池任務提交過程
一個執行緒提交到執行緒池的處理流程如下圖
- 如果此時執行緒池中的數量小於corePoolSize,即使執行緒池中的執行緒都處於空閒狀態,也要建立新的執行緒來處理被新增的任務。
- 如果此時執行緒池中的數量等於corePoolSize,但是緩衝佇列workQueue未滿,那麼任務被放入緩衝佇列。
- 如果此時執行緒池中的數量大於等於corePoolSize,緩衝佇列workQueue滿,並且執行緒池中的數量小於maximumPoolSize,建新的執行緒來處理被新增的任務。
- 如果此時執行緒池中的數量大於corePoolSize,緩衝佇列workQueue滿,並且執行緒池中的數量等於maximumPoolSize,那麼通過 handler所指定的策略來處理此任務。
- 當執行緒池中的執行緒數量大於 corePoolSize時,如果某執行緒空閒時間超過keepAliveTime,執行緒將被終止。這樣,執行緒池可以動態的調整池中的執行緒數。
總結即:處理任務判斷的優先順序為 核心執行緒corePoolSize、任務佇列workQueue、最大執行緒maximumPoolSize,如果三者都滿了,使用handler處理被拒絕的任務。
注意:
- 當workQueue使用的是無界限佇列時,maximumPoolSize引數就變的無意義了,比如new LinkedBlockingQueue(),或者new ArrayBlockingQueue(Integer.MAX_VALUE);
- 使用SynchronousQueue佇列時由於該佇列沒有容量的特性,所以不會對任務進行排隊,如果執行緒池中沒有空閒執行緒,會立即建立一個新執行緒來接收這個任務。maximumPoolSize要設定大一點。
- 核心執行緒和最大執行緒數量相等時keepAliveTime無作用.
3.3. 執行緒池關閉
- shutdown() 不接收新任務,會處理已新增任務
- shutdownNow() 不接受新任務,不處理已新增任務,中斷正在處理的任務
4. 常用佇列介紹
- ArrayBlockingQueue: 這是一個由陣列實現的容量固定的有界阻塞佇列.
- SynchronousQueue: 沒有容量,不能快取資料;每個put必須等待一個take; offer()的時候如果沒有另一個執行緒在poll()或者take()的話返回false。
- LinkedBlockingQueue: 這是一個由單鏈表實現的預設無界的阻塞佇列。LinkedBlockingQueue提供了一個可選有界的建構函式,而在未指明容量時,容量預設為Integer.MAX_VALUE。
佇列操作:
方法 | 說明 |
---|---|
add | 增加一個元索; 如果佇列已滿,則丟擲一個異常 |
remove | 移除並返回佇列頭部的元素; 如果佇列為空,則丟擲一個異常 |
offer | 新增一個元素並返回true; 如果佇列已滿,則返回false |
poll | 移除並返回佇列頭部的元素; 如果佇列為空,則返回null |
put | 新增一個元素; 如果佇列滿,則阻塞 |
take | 移除並返回佇列頭部的元素; 如果佇列為空,則阻塞 |
element | 返回佇列頭部的元素; 如果佇列為空,則丟擲一個異常 |
peek | 返回佇列頭部的元素; 如果佇列為空,則返回null |
5. Executors執行緒工廠類
-
Executors.newCachedThreadPool();
說明: 建立一個可快取執行緒池,如果執行緒池長度超過處理需要,可靈活回收空閒執行緒,若無可回收,則新建執行緒.
內部實現:new ThreadPoolExecutor(0,Integer.MAX_VALUE,60L,TimeUnit.SECONDS,new SynchronousQueue()); -
Executors.newFixedThreadPool(int);
說明: 建立一個定長執行緒池,可控制執行緒最大併發數,超出的執行緒會在佇列中等待。
內部實現:new ThreadPoolExecutor(nThreads, nThreads,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue()); -
Executors.newSingleThreadExecutor();
說明:建立一個單執行緒化的執行緒池,它只會用唯一的工作執行緒來執行任務,保證所有任務按照順序執行。
內部實現:new ThreadPoolExecutor(1,1,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue()) -
Executors.newScheduledThreadPool(int);
說明:建立一個定長執行緒池,支援定時及週期性任務執行。
內部實現:new ScheduledThreadPoolExecutor(corePoolSize)
【附】阿里巴巴Java開發手冊中對執行緒池的使用規範
-
【強制】建立執行緒或執行緒池時請指定有意義的執行緒名稱,方便出錯時回溯。
正例:public class TimerTaskThread extends Thread { public TimerTaskThread(){ super.setName("TimerTaskThread"); ... } }
-
【強制】執行緒資源必須通過執行緒池提供,不允許在應用中自行顯式建立執行緒。
說明: 使用執行緒池的好處是減少在建立和銷燬執行緒上所花的時間以及系統資源的開銷,解決資
源不足的問題。如果不使用執行緒池,有可能造成系統建立大量同類執行緒而導致消耗完記憶體或者
“過度切換”的問題。 - 【強制】執行緒池不允許使用 Executors 去建立,而是通過 ThreadPoolExecutor 的方式,這樣
的處理方式讓寫的同學更加明確執行緒池的執行規則,規避資源耗盡的風險。
說明: Executors 返回的執行緒池物件的弊端如下:
1) FixedThreadPool 和 SingleThreadPool:
允許的請求佇列長度為 Integer.MAX_VALUE,可能會堆積大量的請求,從而導致 OOM。
2) CachedThreadPool 和 ScheduledThreadPool:
允許的建立執行緒數量為 Integer.MAX_VALUE, 可能會建立大量的執行緒,從而導致 OOM。
6. 總結
ThreadPoolExecutor通過幾個核心引數來定義不同型別的執行緒池,適用於不同的使用場景;其中在任務提交時,會依次判斷corePoolSize, workQueque, 及maximumPoolSize,不同的狀態不同的處理。技術領域水太深,如果不是日常使用,基本一段時間後某些知識點就忘的差不多了,因此階段性地回顧與總結,對夯實自己的技術基礎很有必要。
我的個人部落格地址:http://blog.jboost.cn
我的微信公眾號:jboost-ksxy (一個不只有技術乾貨的公眾號,歡迎關注,及時獲取更新內容)
————————————————————————————————————