1. 程式人生 > >架構:執行緒池的實現原理。

架構:執行緒池的實現原理。

Java中的執行緒池是運用場景最多的併發框架,幾乎所有需要非同步或併發執行任務的程式都可以使用執行緒池。在開發過程中,合理的使用執行緒池能夠帶來3個好處。

  • 第一:降低資源消耗。通過重複利用已建立的執行緒降低執行緒建立和銷燬造成的消耗。
  • 第二:提高響應速度。當任務到達時,任務可以不需要等到執行緒建立就能立即執行。
  • 第三:提高執行緒的可管理性。執行緒是稀缺資源,如果無限制的建立,不僅會消耗系統資源,還會降低系統的穩定性,使用執行緒池可以進行統一分配、調優和監控。但是,要做到合理利用執行緒池,必須對其實現原理了如指掌。

執行緒池的實現原理

當向執行緒池提交一個任務之後,執行緒池是如何處理這個任務的呢?下面看一下執行緒池的主要處理流程,處理流程圖如下所示。

從上圖中可以看出,當提交一個新任務到執行緒池時,執行緒池的處理流程如下。

  1. 執行緒池判斷核心執行緒池裡的執行緒是否都在執行任務。如果不是,則建立一個新的工作執行緒來執行任務。如果核心執行緒池裡的執行緒都在執行任務,則進入下個流程。
  2. 執行緒池判斷工作佇列是否已經滿。如果工作佇列沒有滿,則將新提交的任務儲存在這個工作佇列裡。如果工作佇列滿了,則進入下個流程。
  3. 執行緒池判斷執行緒池的執行緒是否都處於工作狀態,如果沒有,則建立一個新的工作執行緒來執行任務。如果已經滿了,則交給飽和策略來處理這個任務。

ThreadPoolExecutor執行execute()方法的示意圖,如下所示。

ThreadPoolExecutor執行execute方法分下面4種情況。

  • 如果當前執行的執行緒少於corePoolSize,則建立新執行緒來執行任務(注意,執行這一步驟需要獲取全域性鎖)。
  • 如果執行的執行緒等於或多於corePoolsize,則將任務加入BlockingQueue。
  • 如果無法將任務加入BlockingQueue(佇列已滿),則建立新的執行緒來處理任務(注意,執行這一步驟需要獲取全域性鎖)。
  • 如果建立新執行緒將使當前執行的執行緒超出maximumPoolSize,任務將被拒絕,並呼叫RejectedExecutionHandler.rejectedExecution()方法。

ThreadPoolExecutor採取上述步驟的總體設計思路,是為了在執行execute()方法時,儘可能的避免獲取全域性鎖(那將會是一個嚴重的可伸縮瓶頸)。在ThreadPoolExecutor完成預熱之後(當前執行的執行緒數大於等於corePoolSize),幾乎所有的execute()方法呼叫都是執行步驟2,而步驟2不需要獲取全域性鎖。

原始碼分析:上面的流程分析讓我們很直觀的瞭解了執行緒池的工作原理,讓我們再通過原始碼來看看如何實現的,執行緒池執行任務的方法如下。

public void execute(Runnable command) {
    if(command == null) throw new NullPointerException();
    // 如果執行緒數小於基本執行緒數,則建立執行緒並執行當前任務
    if(poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
        // 如執行緒數大於等於基本執行緒數或執行緒建立失敗,則將當前任務放到工作佇列中。
        if(runState == RUNNING && workQueue.offer(command)) {
            if(runState != RUNNING || poolSize == 0) ensureQueuedTaskHandled(command);
        }
        // 如果執行緒池不處於執行中或任務無法放入佇列,並且當前執行緒數量小於最大允許的執行緒數量,
        // 則建立一個執行緒執行任務。
        else if(!addIfUnderCorePoolSize(command))
        // 丟擲RejectedExecutionException異常
        reject(command); // is shutdown or saturated
    }
}

工作執行緒:執行緒池建立執行緒時,會將執行緒封裝成工作執行緒Worker,Worker在執行完任務後,還會迴圈獲取工作佇列裡的任務來執行。我們可以從Worker類的run()方法裡看到這點。

public void run() {
    try {
        Runnable task = firstTask;
        firstTask = null;
        while(task != null || (task =getTask()) != null) {
            runTask(task);
            task = null;
        }
    } finally {
        workerDone(this);
    }
}

ThreadPoolExecutor中執行緒執行任務的示意圖如下所示。

執行緒池中的執行緒執行任務分兩種情況,如下。

  • 在execute()方法中建立一個執行緒時,會讓這個執行緒執行當前任務。
  • 這個執行緒執行完上圖中1的任務後,會反覆從BlockingQueue獲取任務來執行。