Java執行緒池ThreadPoolExecutor原始碼解析

不會上豬的樹發表於2023-12-27

Java執行緒池ThreadPoolExecutor原始碼解析

1.ThreadPoolExecutor的構造實現

以jdk8為準,常說執行緒池有七大引數,通常而言,有四個引數是比較重要的

  public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }
  • corePoolSize:核心執行緒數,具體含義理解程式碼
  • maximumPoolSize:最大執行緒數
  • keepAliveTime:執行緒空閒的存活時間
  • unit:時間單位
  • BlockingQueue:阻塞佇列,用來儲存等待執行的任務

接下來去看完整引數的構造實現:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
  • ThreadFactory:執行緒工廠,用來創造執行緒

  • RejectedExecutionHandler:拒絕策略

    1. 如果核心執行緒數等其他引數非法化就會丟擲相應的異常

       if (corePoolSize < 0 ||
                  maximumPoolSize <= 0 ||
                  maximumPoolSize < corePoolSize ||
                  keepAliveTime < 0)
                  throw new IllegalArgumentException();
              if (workQueue == null || threadFactory == null || handler == null)
                  throw new NullPointerException();
      
    2. 之後進行初始化賦值

       this.acc = System.getSecurityManager() == null ?
                      null :
                      AccessController.getContext();
              this.corePoolSize = corePoolSize;
              this.maximumPoolSize = maximumPoolSize;
              this.workQueue = workQueue;
              this.keepAliveTime = unit.toNanos(keepAliveTime);
              this.threadFactory = threadFactory;
              this.handler = handler;
      
      • 注:acc是一個成員變數,用來管理執行緒池中執行緒的訪問控制上下文,其實現類是AccessControlContext

2.執行緒池的執行execute

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        
        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);
    }

總共大致分為三步:要想理解執行緒池的執行,要先去理解控制欄位其具體含義

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

// runState is stored in the high-order bits
private static final int RUNNING    = -1 << COUNT_BITS;
private static final int SHUTDOWN   =  0 << COUNT_BITS;
private static final int STOP       =  1 << COUNT_BITS;
private static final int TIDYING    =  2 << COUNT_BITS;
private static final int TERMINATED =  3 << COUNT_BITS;

// Packing and unpacking ctl
private static int runStateOf(int c)     { return c & ~CAPACITY; }
private static int workerCountOf(int c)  { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }

先宣告執行緒池的五種狀態,再看其他欄位方法具體執行了其他什麼操作

  • RUNNING: -1 << COUNT_BITS,即高3位為111
  • SHUTDOWN: 0 << COUNT_BITS,即高3位為000
  • STOP : 1 << COUNT_BITS,即高3位為001
  • TIDYING : 2 << COUNT_BITS,即高3位為010
  • TERMINATED: 3 << COUNT_BITS,即高3位為011

至於其每種空置狀態的具體意義,根據英文釋義結合程式碼具體理解,而非直接理解,透過位移位的操作將高3位與低29位分離開來,高三位表示此時整個執行緒池的執行狀態,低29位表示執行緒池中執行緒的數量,再去看execute執行過程即可.

int c = ctl.get(); 
if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
  • 用於獲取此時執行緒中的執行緒數,如果小於核心執行緒數,就新增任務,新增任務成功則返回,失敗則重新獲取控制欄位,addworker後續瞭解,複雜的東西簡單化,理解大致操作思想最為核心.
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);
        }
  • 根據控制欄位c去判斷執行緒池的執行狀態是否正在執行,如果新增任務成功則不會執行失敗,或者說此時執行緒數有可能已經大於了核心執行緒數也有可能走到這,所以會將任務新增到阻塞佇列中去,然後重新去獲得控制欄位,再去做校驗,如果此時執行緒池不是正在執行的狀態並且刪除任務成功,這一步主要是為了防止阻塞佇列新增任務成功這個過程,可能執行緒池不執行了,那麼這時候就需要將新增的那個任務刪除,並對他執行拒絕策略,又或者是此時執行緒池中的執行緒數已經為0,說明沒有執行緒在工作了,因此新增一個空任務,至於第二個引數在addWorker中在做說明
else if (!addWorker(command, false))
            reject(command);
  • 字面意思就是新增任務失敗,執行拒絕策略,則是為了應對執行緒池已經到了滿負載的狀態

3.執行緒池的任務新增addworker

private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

            for (;;) {
                int wc = workerCountOf(c);
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs)
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }

        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                    // Recheck while holding lock.
                    // Back out on ThreadFactory failure or if
                    // shut down before lock acquired.
                    int rs = runStateOf(ctl.get());

                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        workers.add(w);
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

程式碼量很長,但是大致可分為兩部分,且邏輯很清晰

  1. 這裡使用了標籤語法,前半段大致是是否需要新增執行緒做一系列準備

     retry:
            for (;;) {
                int c = ctl.get();
                int rs = runStateOf(c);
    
                // Check if queue empty only if necessary.
                if (rs >= SHUTDOWN &&
                    ! (rs == SHUTDOWN &&
                       firstTask == null &&
                       ! workQueue.isEmpty()))
                    return false;
    
                for (;;) {
                    int wc = workerCountOf(c);
                    if (wc >= CAPACITY ||
                        wc >= (core ? corePoolSize : maximumPoolSize))
                        return false;
                    if (compareAndIncrementWorkerCount(c))
                        break retry;
                    c = ctl.get();  // Re-read ctl
                    if (runStateOf(c) != rs)
                        continue retry;
                    // else CAS failed due to workerCount change; retry inner loop
                }
            }
    
    1. 獲取控制欄位c:這個欄位包含了執行狀態資訊和執行緒池數量資訊,是一個複合欄位,而rs則是獲取高三位的執行緒池狀態資訊
    2. 根據前面執行緒池狀態資訊,執行RUNNING值最小,因此判斷執行緒池如果處於非執行的狀態,則去判斷是否處於關閉的狀態,判斷第一個任務是否為空,佇列不為空,但是由於前面取反操作,其真正含義是:如果執行緒池的狀態不是 SHUTDOWN,或者任務佇列為空,或者有待執行的任務,那麼就不會拒絕新任務的提交,否則就返回false,表示新增任務失敗
    3. 接下來死迴圈表示需要去新增執行的任務,首先獲取執行緒池中的執行緒數,關鍵的地方在這,如果此時的執行緒數大於等於容量或者(這裡根據傳遞的引數core來選則比較的目標是核心執行緒數還是最大執行緒數),比較失敗,則說明超過了接受的範疇,新增任務失敗,如果沒有失敗,則透過底層CAS操作使得執行緒數加1,然後直接結束呼叫,跳出迴圈,,如果CAS失敗,則說明ctl欄位受到了變化,此期間有其他任務參與,重新獲取此欄位,去判斷一下重新獲取的ctl欄位和之前的rs欄位是否相等,這是為了保障多執行緒情況下出現的一種併發競爭問題導致的執行緒數發生錯亂.
  2. 至此,上半部分的核心已經解決,下半部分此時真正去實現任務的新增,透過執行緒池中的內部類Worker去實現

      boolean workerStarted = false;
            boolean workerAdded = false;
            Worker w = null;
            try {
                w = new Worker(firstTask);
                final Thread t = w.thread;
                if (t != null) {
                    final ReentrantLock mainLock = this.mainLock;
                    mainLock.lock();
                    try {
                        // Recheck while holding lock.
                        // Back out on ThreadFactory failure or if
                        // shut down before lock acquired.
                        int rs = runStateOf(ctl.get());
    
                        if (rs < SHUTDOWN ||
                            (rs == SHUTDOWN && firstTask == null)) {
                            if (t.isAlive()) // precheck that t is startable
                                throw new IllegalThreadStateException();
                            workers.add(w);
                            int s = workers.size();
                            if (s > largestPoolSize)
                                largestPoolSize = s;
                            workerAdded = true;
                        }
                    } finally {
                        mainLock.unlock();
                    }
                    if (workerAdded) {
                        t.start();
                        workerStarted = true;
                    }
                }
            } finally {
                if (! workerStarted)
                    addWorkerFailed(w);
            }
            return workerStarted;
        }
    
    1. 兩個布林型別暫時不用管,大致猜測意思即可,將firstask任務交付給worker,由worker內部的thread執行緒去執行,因此需要去理解worker的實現

      3.1Worker內部的工作者

      3.11構造方法實現

      Worker(Runnable firstTask) {
          setState(-1); // inhibit interrupts until runWorker
          this.firstTask = firstTask;
          this.thread = getThreadFactory().newThread(this);
      }
      

      接收一個Runnable引數做為任務進行初始化,這裡用到了AQS的一些實現,然後透過執行緒工廠創造一個新的執行緒,賦予給內部的成員變數引用

      • 還有一些鎖的一些操作,後續再看

    2. 如果工作者的內部執行緒已經被創造好,實現就緒,要先獲得執行緒池的互斥鎖,然後對接下來的操作進行互斥訪問

    3. 重新獲取最新的執行緒池的執行狀態,只有當執行緒池處於執行狀態或者處於關閉狀態但沒有待執行的任務時,才能將新工作執行緒新增到執行緒池中,也就是worker中去,因此一個worker內部具備一個thread,如果想要實現許多執行緒去完成執行緒池的相應操作,需要將worker封裝成集合,因此執行緒池內部還有一成員變數:

      private final HashSet<Worker> workers = new HashSet<Worker>();
      

      這樣就確保了每一個worker都是獨一無二的,不會重複的,也就意味著每一個執行緒都不一樣.

    4. 而最後一個largestPoolSize則是保留歷史的最大執行緒數的,用來記錄,至此就已經新增成功了,只不過此時還沒有執行

    5. 之後解鎖,用之前標誌位workerAdded表示新增成功,然後啟動執行緒,也就是去執行這個任務,再用另一個標誌workerStarted表示啟動成功.

    6. 最後則是檢查是否有什麼異常在啟動期間,如果沒啟動成功,則呼叫addWorkerFiled方法去處理

      3.12 addWorkerFailed方法

      private void addWorkerFailed(Worker w) {
          final ReentrantLock mainLock = this.mainLock;
          mainLock.lock();
          try {
              if (w != null)
                  workers.remove(w);
              decrementWorkerCount();
              tryTerminate();
          } finally {
              mainLock.unlock();
          }
      }
      
      • 緊接上文,也就是啟動失敗的話,會將執行任務的workerremove(底層透過HashMap實現鍵的刪除),然後減少執行緒數,等待一會,這個過程是互斥的,因為牽扯到控制欄位
    7. 至此,新增任務如果成功,則進行執行,如果成功開啟執行,則成功返回

    因此,根據執行緒池的執行新增流程,大致可以將此過程透過繪圖的方式表現出來:

    執行緒池


4.工作者的run方法是如何執行的

worker中還有一個方法

  public void run() {
            runWorker(this);
        }

是其執行的具體操作

final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            while (task != null || (task = getTask()) != null) {
                w.lock();
                // If pool is stopping, ensure thread is interrupted;
                // if not, ensure thread is not interrupted.  This
                // requires a recheck in second case to deal with
                // shutdownNow race while clearing interrupt
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                        task.run();
                    } catch (RuntimeException x) {
                        thrown = x; throw x;
                    } catch (Error x) {
                        thrown = x; throw x;
                    } catch (Throwable x) {
                        thrown = x; throw new Error(x);
                    } finally {
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }
  • 上鎖之前的操作都很容易看懂,處了getTask,這個方法用來獲取阻塞佇列中的任務,後續再理解
    1. 首先看第一段if,就是用來檢視此時執行緒池的狀態,如果不處於關閉或者執行的狀態,或者執行緒處於中斷的狀態,則確保執行緒中斷
    2. 接下來是一部分異常和錯誤的處理以及執行一些前置任務和一些後置任務
    3. 最後完成的任務數加一,解鎖,將標誌位是否中斷,改為false,表示執行成功.

5.獲取任務的getTask方法

private Runnable getTask() {
    boolean timedOut = false; // Did the last poll() time out?

    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

        // Check if queue empty only if necessary.
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }

        int wc = workerCountOf(c);

        // Are workers subject to culling?
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }

        try {
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                return r;
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}
  1. 首先標誌位用來判斷是否超時,預設情況下不超時,跟之前的引數掛鉤,後續再看,然後進入死迴圈,不斷迴圈去執行後續操作

  2. 獲取控制位c和rs執行狀態,之後的if操作含義是如果執行緒池處於關閉的狀態或者此時佇列為空,就說明沒有任務需要處理了,此時讓執行緒池中的執行緒數減一,返回,另一種情況則是執行緒池的狀態處於關閉狀態之上,則說明執行緒池現在不執行任務了,不需要管佇列中是否還有任務存在,則同上減一,返回。

  3. 然後重新獲取執行緒池的執行緒數,接下來的time布林這個欄位有些作用,後面的allowCoreThreadTimeOut是一個控制欄位,用來表示執行緒是否允許超時而返回的一個欄位,如:執行緒池中的核心執行緒如果因為長時間沒有得到任務的滋養,就如同執行緒之間會發生飢餓一樣,因此存在一個欄位用來控制超時是否生效.因此如果執行緒數大於核心執行緒數或者開啟超時控制欄位,就說明會執行超時退出.

  4. 接下來的if判斷是用來執行遞減執行緒數的一個操作,底層採取CAS就不多說了,wc > maximumPoolSize:用來表示如果大於了最大執行緒數,說明需要減少執行緒數,至於為什麼會出現這種情況,等會理解.(timed && timedOut):說明開啟超時退出,且上一次獲取任務因超時返回,這個需看後面程式碼理解.上面的兩個條件滿足其中之一即可.wc > 1 || workQueue.isEmpty())而這個操作則是為了減少不必要的執行緒開銷,如果阻塞佇列為空說明沒有任務,那自然不需要多餘的執行緒數去執行,因此會發生接下來的操作,遞減執行緒數,然後跳到下一次迴圈.

  5. 之後接下來就是從阻塞佇列中獲取任務的核心了,第一步是根據超時控制欄位來決定行為方式,允許超時退出的話,透過poll方式,不允許則透過take方式,兩種方式大致是一個等待一定時間,如果為空是前提.另一個是無限等待,會阻塞執行緒.其具體實現透過阻塞佇列的真正實現類別去實現.如果獲取到了任務,就返回,如果沒有則timeout設定為true,表示沒有接受到任務,因此前文的timeout就理解了.

    通常而言執行緒池中的執行緒數是不允許超過最大執行緒數的,但通常而言這是一種機制的完整性和規範,假如是自定義執行緒池的情況下,就有可能出現這種情況,另外一種是本人推測雖然由於增加工作執行緒數的操作底層是透過CAS去實現的,底層是原子性的,同時進行CAS操作就有可能導致ABA問題出現,或者操作失敗,或者不斷自旋的可能,


6.任務的提交submit

眾所周知,任務需要進行提交給執行緒池,再有執行緒池去執行,而Runnable介面實現的run方法是沒有返回值的,而線上程中Callable通常具備返回值,且配備Future去接受結果.因此submit具備不同的操作

這裡以AbstractExecutorService(執行緒池的父類)介面為例:

public Future<?> submit(Runnable task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        execute(ftask);
        return ftask;
    }

 public <T> Future<T> submit(Runnable task, T result) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task, result);
        execute(ftask);
        return ftask;
    }

public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }
  • RunnableFuture介面的實現類FutureTask,總而言之就是轉換為一個Runnable,然後進行提交,最後返回一個future,至於FutureTask具體內容自行詳解.

7.執行緒池的關閉

public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            advanceRunState(SHUTDOWN);
            interruptIdleWorkers();
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
    }

實現邏輯也很清楚,檢查是否可以關閉執行緒,然後設定執行緒的狀態,interruptIdleWorkers()這個方法算是關鍵的,他會去中斷worker;onShutdown是一個空方法,留給子類去實現的.

 private void interruptIdleWorkers() {
        interruptIdleWorkers(false);
    }
private void interruptIdleWorkers(boolean onlyOne) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers) {
                Thread t = w.thread;
                if (!t.isInterrupted() && w.tryLock()) {
                    try {
                        t.interrupt();
                    } catch (SecurityException ignore) {
                    } finally {
                        w.unlock();
                    }
                }
                if (onlyOne)
                    break;
            }
        } finally {
            mainLock.unlock();
        }
    }

他會去遍歷集合workers,獲取每一個worker的工作執行緒,然後嘗試去中斷,最後結束.

相關文章