Java Thread&Concurrency(11): 深入理解ThreadPoolExecutor及其實現原理

背景(註釋):

一個ExecutorService通過使用不同的線程池來執行提交的任務,線程池可以通過Executors來配置。

線程池主要解決兩個問題:改善執行大量任務時的性能(通過一個任務/調用的模式)和提供資源方面(比如線程、執行的任務總數統計)的限制和管理。

Executors提供了幾種常用的執行器:

  • Executors.newCachedThreadPool:無限制線程池,自動線程重用。
  • Executors.newFixedThreadPool:固定數量線程池。
  • Executors.newSingleThreadPool:單個工作線程。
以上的這些線程可以滿足常用的場景了。

飽和策略:
當心的任務通過execute提交之後,假如當前執行器已經被關閉,但是當前還有工作線程以及隊列中還有待執行任務。那麼我們的會執行RejectedExecutionHandler的rejectedExecution方法,有四種策略如下:
  • 默認的拒絕策略(AbortPolicy),通過拋出一個RejectedExecutionException異常來拒絕。
  • (CallerRunsPolicy),通過調用者自己執行任務來避免任務太多。
  • (DiscardPolicy),直接忽略任務。
  • (DiscardOldestPolicy),如果當前執行器還沒有關閉,那麼當前隊列中存在最久的任務會被忽略,然後重試。

擴展:
以下是一個有趣的執行器,通過它我們可以在外部暫停/重啓執行器的工作

class PausableThreadPoolExecutor extends ThreadPoolExecutor {
    private boolean isPaused;
    private ReentrantLock pauseLock = new ReentrantLock();
    private Condition unpaused = pauseLock.newCondition();
 
    public PausableThreadPoolExecutor(...) { super(...); }
 
    protected void beforeExecute(Thread t, Runnable r) {
      super.beforeExecute(t, r);
      pauseLock.lock();
      try {
        while (isPaused) unpaused.await();
      } catch (InterruptedException ie) {
        t.interrupt();
      } finally {
        pauseLock.unlock();
      }
    }
 
    public void pause() {
      pauseLock.lock();
      try {
        isPaused = true;
      } finally {
        pauseLock.unlock();
      }
    }
 
    public void resume() {
      pauseLock.lock();
      try {
        isPaused = false;
        unpaused.signalAll();
      } finally {
        pauseLock.unlock();
      }
    }
  }

實現:

由於這個實現並不複雜,所以我們僅分析execute操作和證明--不可能存在隊列中的任務殘留。

首先來看execute:

    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
    }
事實上這裏的註釋已經說得很清楚了,詳情如下:

  • 首先如果工作線程數小於corePoolSize,那麼試着添加一個新的工作線程來執行任務。這個過程會檢查運行狀態和工作者線程數,從而避免錯誤或者過多的工作者線程。
  • 假如工作線程已滿,那麼我們就提交任務至隊列,接着判斷此刻的執行器狀態,假如已經被關閉就嘗試刪除該任務並拒絕,否則假如此刻不存在工作線程就創建一個。
  • 否則在工作列隊已滿的情況下可以查看是否可以創建一個新的工作線程,不行則拒絕任務。


隊列中不會殘留任務:

如上代碼所示,假如存在一種情景:隊列中還有任務,但是再也不會有工作隊列去執行。我們來看看如何產生。

首先可以顯然看出,SHUTDOWN狀態不會對已經提交到隊列的任務產生任何影響。

  • 假如任務的提交是作爲addWorker的方式成功調用,那麼必定被執行。
  • 假如任務是提交至隊列,並被工作線程得到(take或者poll)那麼必定被執行
  • 假如任務提交至隊列之後,注意噹噹前工作線程數爲0,則會通過addWorker方法,若成功則任務必定會被執行,若失敗則說明有其他的調用產生了工作線程或者執行器終止(STOP),前者會執行任務,後者不管。
  • 所以僅在工作線程的poll限時操作沒有注意到存在任務,並且提交任務的調用發現還存在工作線程,在這種情況下,這個工作線程就會錯失任務的信號。
  • 所以最後的關鍵就是processWorkerExit方法,在其中會再次判斷當前的隊列狀態,從而能夠再次生成一個工作線程來探測任務。
  • 事實上,執行器在shutdown之前會穩定維護着1個或者corePoolSize個工作線程(前提是達到過這麼多工作線程)。



 
    

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章