揭祕Java執行緒池的真相
執行緒池是指在初始化一個多執行緒應用程式過程中建立一個執行緒集合,然後在需要執行新的任務時重用這些執行緒而不是新建一個執行緒。執行緒池中執行緒的數量通常完全取決於可用記憶體數量和應用程式的需求。然而,增加可用執行緒數量是可能的。執行緒池中的每個執行緒都有被分配一個任務,一旦任務已經完成了,執行緒回到池子中並等待下一次分配任務。
執行緒池的使用場景
場景一:
一個業務邏輯有很多次的迴圈,每次迴圈之間沒有影響,比如驗證1萬條url路徑是否存在,正常情況要迴圈1萬次,逐個去驗證每一條URL, 這樣效率會很低,假設驗證一條需要1分鐘,總共就需要1萬分鍾,有點恐怖。這時可以用多執行緒,將1萬條URL分成50等份,開50個執行緒,沒個執行緒只需驗證200條,這樣所有的執行緒執行完是遠小於1萬分鐘的。
場景二:
需要知道一個任務的執行進度,比如我們常看到的進度條,實現方式可以是在任務中加入一個整型屬性變數(這樣不同方法可以共享),任務執行一定程度就給變數值加1,另外開一個執行緒按時間間隔不斷去訪問這個變數,並反饋給使用者。
總之使用多執行緒就是為了充分利用cpu的資源,提高程式執行效率,當你發現一個業務邏輯執行效率特別低,耗時特別長,就可以考慮使用多執行緒。不過CPU執行哪個執行緒的時間和順序是不確定的,即使設定了執行緒的優先順序,因此使用多執行緒的風險也是比較大的,會出現很多預料不到的問題,一定要多熟悉概念,多構造不同的場景去測試才能夠掌握。
使用執行緒池的好處
1.降低資源消耗。通過重複利用已建立的執行緒降低執行緒建立和銷燬造成的損耗。
2.提供響應速度。當任務到達時,任務可以不需要等待執行緒建立就能夠立即執行。
3.提供執行緒的可管控性。合理利用執行緒池,必須瞭解它的實現原理。執行緒是稀缺資源,如果無限制的使用,不僅會消耗系統資源,還會降低系統的穩定性。使用執行緒池可以統一分配、調優和監控。
執行緒池的實現原理和工作機制
當向執行緒提交一個任務之後,執行緒池是如何處理這個任務的呢,讓我們一起來揭祕執行緒池的處理流程,如下圖所示:
當提交一個新任務到執行緒池時,流程如下:
- 判斷核心執行緒池裡的執行緒是否都在執行任務。如果不是,則建立一個新的工作執行緒來執行任務。如果都在執行,則進入下一個流程。
- 判斷工作佇列是否已經滿了。如果還沒有滿,則將新提交的任務儲存在這個工作佇列裡面。如果滿了,則進入下一個流程。
- 判斷執行緒池的執行緒是否都處於工作狀態。如果沒有,則建立一個新的工作執行緒來執行任務。如果已經滿了,則交給飽和策略來處理這個任務。
ThreadPoolExecutor執行execute方法的主要處理流程示意圖如下所示:
ThreadPoolExecutor執行execute存在四大場景:
- 如果當前執行的執行緒少於corePoolSize,則建立新執行緒來執行任務(需要獲取全域性鎖)。
- 如果執行的執行緒等於或多於corePoolSize,則將任務加入BlockingQueue。
- 如果無法將任務加入到BlockingQueue(佇列已滿時),則建立新的執行緒來處理(需要獲取全域性鎖)。
- 如果建立新執行緒將使當前執行的執行緒超出maximumPoolSize,任務將被拒絕,並呼叫RejectedExecutionHandler.rejectedExecution()方法。
Worker:ThreadPoolExecutor的一個內部類,實現了AbstractQueuedSynchronizer抽象類。
全域性鎖:即允許一個執行緒呼叫執行的時候讓 其它執行緒處於等待狀態.。
ThreadPoolExecutor採用以上場景,是為了執行execute()方法時,儘可能地避免獲取全域性鎖(一個嚴重的可伸縮瓶頸)。當ThreadPoolExecutor執行的執行緒大於或等於corePoolSize時,幾乎所有的execute()方法都將會呼叫第二場景,而且第二場景是不需要獲取全域性鎖的。
原始碼分析:上面的流程分析讓我們很直觀的瞭解了執行緒池的工作原理。再讓我們通過原理了解一下它的實現原理。
public void execute(Runnable command) {
if (command == null) throw new NullPointerException();
//--如果核心執行緒已經用盡( poolSize >= corePoolSize ), 進入if分支
//--核心執行緒沒用完,立即執行執行緒,不進入if分支(!addIfUnderCorePoolSize(command))
if (poolSize >= corePoolSize /*核心執行緒沒空餘*/ || !addIfUnderCorePoolSize(command)/*核心執行緒有空餘*/)
{
//--如果核心執行緒已經用盡, 並且runState == RUNNING, 則新增command到workQueue中去
if (runState == RUNNING && workQueue.offer(command)) {
if (runState != RUNNING || poolSize == 0)
//如果執行緒池狀態不為RUNNING,或者正在工作的執行緒個數為0,則儘量保證新增的Command被處理(Reject)
ensureQueuedTaskHandled(command);
}
//--如果workQueue已滿,則嘗試直接執行執行緒(需要保證當前執行執行緒<maximumPoolSize,感覺好像插隊了一樣,不公平)
else if (!addIfUnderMaximumPoolSize(command))
//如果poolSize>=maximumPoolSize,則拒絕(通過丟擲執行時異常)
reject(command); // is shutdown or saturated
}
}
工作執行緒:執行緒池建立執行緒時,會將執行緒封裝成工作執行緒Worker,Worker在執行完任務後,還會迴圈獲取工作佇列裡的任務來執行。我們可以從Worker類的run()方法看到這點。
//Worker
public void run() {
try {
Runnable task = firstTask;
firstTask = null;
//task = getTask()就是嘗試從佇列中取執行緒來執行
while (task != null || (task = getTask()) != null) {
runTask(task);
task = null;
}
} finally {
workerDone(this);
}
}
ThreadPoolExecutor中執行緒執行任務的示意圖如下所示:
ThreadPoolExecutor中執行緒執行任務分為兩種情況:
- 在執行execute()方法建立一個執行緒,會讓這個執行緒執行當前任務。
- 在這個執行緒執行完上圖1的任務之後,會反覆從BlockingQueue來獲取任務來執行。
---------------------------------------------很多東西寧缺毋濫,流星的光芒短暫而灼熱閃耀。---------------------------------------------