硬核乾貨:4W字從原始碼上分析JUC執行緒池ThreadPoolExecutor的實現原理

throwable發表於2020-08-27

前提

很早之前就打算看一次JUC執行緒池ThreadPoolExecutor的原始碼實現,由於近段時間比較忙,一直沒有時間整理出原始碼分析的文章。之前在分析擴充套件執行緒池實現可回撥的Future時候曾經提到併發大師Doug Lea在設計執行緒池ThreadPoolExecutor的提交任務的頂層介面Executor只有一個無狀態的執行方法:

public interface Executor {

    void execute(Runnable command);
}    

ExecutorService提供了很多擴充套件方法底層基本上是基於Executor#execute()方法進行擴充套件。本文著重分析ThreadPoolExecutor#execute()的實現,筆者會從實現原理、原始碼實現等角度結合簡化例子進行詳細的分析。ThreadPoolExecutor的原始碼從JDK8JDK11基本沒有變化,本文編寫的時候使用的是JDK11

ThreadPoolExecutor的原理

ThreadPoolExecutor裡面使用到JUC同步器框架AbstractQueuedSynchronizer(俗稱AQS)、大量的位操作、CAS操作。ThreadPoolExecutor提供了固定活躍執行緒(核心執行緒)、額外的執行緒(執行緒池容量 - 核心執行緒數這部分額外建立的執行緒,下面稱為非核心執行緒)、任務佇列以及拒絕策略這幾個重要的功能。

JUC同步器框架

ThreadPoolExecutor裡面使用到JUC同步器框架,主要用於四個方面:

  • 全域性鎖mainLock成員屬性,是可重入鎖ReentrantLock型別,主要是用於訪問工作執行緒Worker集合和進行資料統計記錄時候的加鎖操作。
  • 條件變數terminationCondition型別,主要用於執行緒進行等待終結awaitTermination()方法時的帶期限阻塞。
  • 任務佇列workQueueBlockingQueue<Runnable>型別,任務佇列,用於存放待執行的任務。
  • 工作執行緒,內部類Worker型別,是執行緒池中真正的工作執行緒物件。

關於AQS筆者之前寫過一篇相關原始碼分析的文章:JUC同步器框架AbstractQueuedSynchronizer原始碼圖文分析

核心執行緒

這裡先參考ThreadPoolExecutor的實現並且進行簡化,實現一個只有核心執行緒的執行緒池,要求如下:

  • 暫時不考慮任務執行異常情況下的處理。
  • 任務佇列為無界佇列。
  • 執行緒池容量固定為核心執行緒數量。
  • 暫時不考慮拒絕策略。
public class CoreThreadPool implements Executor {

    private BlockingQueue<Runnable> workQueue;
    private static final AtomicInteger COUNTER = new AtomicInteger();
    private int coreSize;
    private int threadCount = 0;

    public CoreThreadPool(int coreSize) {
        this.coreSize = coreSize;
        this.workQueue = new LinkedBlockingQueue<>();
    }

    @Override
    public void execute(Runnable command) {
        if (++threadCount <= coreSize) {
            new Worker(command).start();
        } else {
            try {
                workQueue.put(command);
            } catch (InterruptedException e) {
                throw new IllegalStateException(e);
            }
        }
    }

    private class Worker extends Thread {
        private Runnable firstTask;

        public Worker(Runnable runnable) {
            super(String.format("Worker-%d", COUNTER.getAndIncrement()));
            this.firstTask = runnable;
        }

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

    private Runnable getTask() {
        try {
            return workQueue.take();
        } catch (InterruptedException e) {
            throw new IllegalStateException(e);
        }
    }

    public static void main(String[] args) throws Exception {
        CoreThreadPool pool = new CoreThreadPool(5);
        IntStream.range(0, 10)
                .forEach(i -> pool.execute(() ->
                        System.out.println(String.format("Thread:%s,value:%d", Thread.currentThread().getName(), i))));
        Thread.sleep(Integer.MAX_VALUE);
    }
}

某次執行結果如下:

Thread:Worker-0,value:0
Thread:Worker-3,value:3
Thread:Worker-2,value:2
Thread:Worker-1,value:1
Thread:Worker-4,value:4
Thread:Worker-1,value:5
Thread:Worker-2,value:8
Thread:Worker-4,value:7
Thread:Worker-0,value:6
Thread:Worker-3,value:9

設計此執行緒池的時候,核心執行緒是懶建立的,如果執行緒空閒的時候則阻塞在任務佇列的take()方法,其實對於ThreadPoolExecutor也是類似這樣實現,只是如果使用了keepAliveTime並且允許核心執行緒超時(allowCoreThreadTimeOut設定為true)則會使用BlockingQueue#poll(keepAliveTime)進行輪詢代替永久阻塞。

其他附加功能

構建ThreadPoolExecutor例項的時候,需要定義maximumPoolSize(執行緒池最大執行緒數)和corePoolSize(核心執行緒數)。當任務佇列是有界的阻塞佇列,核心執行緒滿負載,任務佇列已經滿的情況下,會嘗試建立額外的maximumPoolSize - corePoolSize個執行緒去執行新提交的任務。當ThreadPoolExecutor這裡實現的兩個主要附加功能是:

  • 一定條件下會建立非核心執行緒去執行任務,非核心執行緒的回收週期(執行緒生命週期終結時刻)是keepAliveTime,執行緒生命週期終結的條件是:下一次通過任務佇列獲取任務的時候並且存活時間超過keepAliveTime
  • 提供拒絕策略,也就是在核心執行緒滿負載、任務佇列已滿、非核心執行緒滿負載的條件下會觸發拒絕策略。

原始碼分析

先分析執行緒池的關鍵屬性,接著分析其狀態控制,最後重點分析ThreadPoolExecutor#execute()方法。

關鍵屬性

public class ThreadPoolExecutor extends AbstractExecutorService {

    // 控制變數-存放狀態和執行緒數
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

    // 任務佇列,必須是阻塞佇列
    private final BlockingQueue<Runnable> workQueue;

    // 工作執行緒集合,存放執行緒池中所有的(活躍的)工作執行緒,只有在持有全域性鎖mainLock的前提下才能訪問此集合
    private final HashSet<Worker> workers = new HashSet<>();
    
    // 全域性鎖
    private final ReentrantLock mainLock = new ReentrantLock();

    // awaitTermination方法使用的等待條件變數
    private final Condition termination = mainLock.newCondition();

    // 記錄峰值執行緒數
    private int largestPoolSize;
    
    // 記錄已經成功執行完畢的任務數
    private long completedTaskCount;
    
    // 執行緒工廠,用於建立新的執行緒例項
    private volatile ThreadFactory threadFactory;

    // 拒絕執行處理器,對應不同的拒絕策略
    private volatile RejectedExecutionHandler handler;
    
    // 空閒執行緒等待任務的時間週期,單位是納秒
    private volatile long keepAliveTime;
    
    // 是否允許核心執行緒超時,如果為true則keepAliveTime對核心執行緒也生效
    private volatile boolean allowCoreThreadTimeOut;
    
    // 核心執行緒數
    private volatile int corePoolSize;

    // 執行緒池容量
    private volatile int maximumPoolSize;

    // 省略其他程式碼
}    

下面看引數列表最長的建構函式:

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.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

可以自定義核心執行緒數、執行緒池容量(最大執行緒數)、空閒執行緒等待任務週期、任務佇列、執行緒工廠、拒絕策略。下面簡單分析一下每個引數的含義和作用:

  • corePoolSize:int型別,核心執行緒數量。
  • maximumPoolSize:int型別,最大執行緒數量,也就是執行緒池的容量。
  • keepAliveTime:long型別,執行緒空閒等待時間,也和工作執行緒的生命週期有關,下文會分析。
  • unitTimeUnit型別,keepAliveTime引數的時間單位,實際上keepAliveTime最終會轉化為納秒。
  • workQueueBlockingQueue<Runnable>型別,等待佇列或者叫任務佇列。
  • threadFactoryThreadFactory型別,執行緒工廠,用於建立工作執行緒(包括核心執行緒和非核心執行緒),預設使用Executors.defaultThreadFactory()作為內建執行緒工廠例項,一般自定義執行緒工廠才能更好地跟蹤工作執行緒。
  • handlerRejectedExecutionHandler型別,執行緒池的拒絕執行處理器,更多時候稱為拒絕策略,拒絕策略執行的時機是當阻塞佇列已滿、沒有空閒的執行緒(包括核心執行緒和非核心執行緒)並且繼續提交任務。提供了4種內建的拒絕策略實現:
    • AbortPolicy:直接拒絕策略,也就是不會執行任務,直接丟擲RejectedExecutionException,這是預設的拒絕策略
    • DiscardPolicy:拋棄策略,也就是直接忽略提交的任務(通俗來說就是空實現)。
    • DiscardOldestPolicy:拋棄最老任務策略,也就是通過poll()方法取出任務佇列隊頭的任務拋棄,然後執行當前提交的任務。
    • CallerRunsPolicy:呼叫者執行策略,也就是當前呼叫Executor#execute()的執行緒直接呼叫任務Runnable#run()一般不希望任務丟失會選用這種策略,但從實際角度來看,原來的非同步呼叫意圖會退化為同步呼叫

狀態控制

狀態控制主要圍繞原子整型成員變數ctl

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

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;

// 通過ctl值獲取執行狀態
private static int runStateOf(int c)     { return c & ~COUNT_MASK; }
// 通過ctl值獲取工作執行緒數
private static int workerCountOf(int c)  { return c & COUNT_MASK; }

// 通過執行狀態和工作執行緒數計算ctl的值,或運算
private static int ctlOf(int rs, int wc) { return rs | wc; }

private static boolean runStateLessThan(int c, int s) {
    return c < s;
}

private static boolean runStateAtLeast(int c, int s) {
    return c >= s;
}

private static boolean isRunning(int c) {
    return c < SHUTDOWN;
}

// CAS操作執行緒數增加1
private boolean compareAndIncrementWorkerCount(int expect) {
    return ctl.compareAndSet(expect, expect + 1);
}

// CAS操作執行緒數減少1
private boolean compareAndDecrementWorkerCount(int expect) {
    return ctl.compareAndSet(expect, expect - 1);
}

// 執行緒數直接減少1
private void decrementWorkerCount() {
    ctl.addAndGet(-1);
}

接下來分析一下執行緒池的狀態變數,工作執行緒上限數量位的長度是COUNT_BITS,它的值是Integer.SIZE - 3,也就是正整數29:

我們知道,整型包裝型別Integer例項的大小是4 byte,一共32 bit,也就是一共有32個位用於存放0或者1。
在ThreadPoolExecutor實現中,使用32位的整型包裝型別存放工作執行緒數和執行緒池狀態。
其中,低29位用於存放工作執行緒數,而高3位用於存放執行緒池狀態,所以執行緒池的狀態最多隻能有2^3種。
工作執行緒上限數量為2^29 - 1,超過5億,這個數量在短時間內不用考慮會超限。

接著看工作執行緒上限數量掩碼COUNT_MASK,它的值是(1 < COUNT_BITS) - l,也就是1左移29位,再減去1,如果補全32位,它的位檢視如下:

j-u-c-t-p-e-1

然後就是執行緒池的狀態常量,這裡只詳細分析其中一個,其他類同,這裡看RUNNING狀態:

// -1的補碼為:111-11111111111111111111111111111
// 左移29位後:111-00000000000000000000000000000
// 10進位制值為:-536870912 
// 高3位111的值就是表示執行緒池正在處於執行狀態
private static final int RUNNING = -1 << COUNT_BITS;

控制變數ctl的組成就是通過執行緒池執行狀態rs和工作執行緒數wc通過或運算得到的:

// rs=RUNNING值為:111-00000000000000000000000000000
// wc的值為0:000-00000000000000000000000000000
// rs | wc的結果為:111-00000000000000000000000000000
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static int ctlOf(int rs, int wc) { 
    return rs | wc; 
}

那麼我們怎麼從ctl中取出高3位?上面原始碼中提供的runStateOf()方法就是提取執行狀態:

// 先把COUNT_MASK取反(~COUNT_MASK),得到:111-00000000000000000000000000000
// ctl點陣圖特點是:xxx-yyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
// 兩者做一次與運算即可得到高3位xxx
private static int runStateOf(int c){ 
    return c & ~COUNT_MASK; 
}

同理,取出低29位只需要把ctlCOUNT_MASK(000-11111111111111111111111111111)做一次與運算即可。

小結一下執行緒池的執行狀態常量:

狀態名稱 點陣圖 十進位制值 描述
RUNNING 111-00000000000000000000000000000 -536870912 執行中狀態,可以接收新的任務和執行任務佇列中的任務
SHUTDOWN 000-00000000000000000000000000000 0 shutdown狀態,不再接收新的任務,但是會執行任務佇列中的任務
STOP 001-00000000000000000000000000000 536870912 停止狀態,不再接收新的任務,也不會執行任務佇列中的任務,中斷所有執行中的任務
TIDYING 010-00000000000000000000000000000 1073741824 整理中狀態,所有任務已經終結,工作執行緒數為0,過渡到此狀態的工作執行緒會呼叫鉤子方法terminated()
TERMINATED 011-00000000000000000000000000000 1610612736 終結狀態,鉤子方法terminated()執行完畢

這裡有一個比較特殊的技巧,由於執行狀態值存放在高3位,所以可以直接通過十進位制值(甚至可以忽略低29位,直接用ctl進行比較,或者使用ctl和執行緒池狀態常量進行比較)來比較和判斷執行緒池的狀態:

RUNNING(-536870912) < SHUTDOWN(0) < STOP(536870912) < TIDYING(1073741824) < TERMINATED(1610612736)

下面這三個方法就是使用這種技巧:

// ctl和狀態常量比較,判斷是否小於
private static boolean runStateLessThan(int c, int s) {
    return c < s;
}

// ctl和狀態常量比較,判斷是否小於或等於
private static boolean runStateAtLeast(int c, int s) {
    return c >= s;
}

// ctl和狀態常量SHUTDOWN比較,判斷是否處於RUNNING狀態
private static boolean isRunning(int c) {
    return c < SHUTDOWN;
}

最後是執行緒池狀態的躍遷圖:

j-u-c-t-p-e-2

PS:執行緒池原始碼中有很多中間變數用了簡單的單字母表示,例如c就是表示ctl、wc就是表示worker count、rs就是表示running status。

execute方法原始碼分析

執行緒池非同步執行任務的方法實現是ThreadPoolExecutor#execute(),原始碼如下:

// 執行命令,其中命令(下面稱任務)物件是Runnable的例項
public void execute(Runnable command) {
    // 判斷命令(任務)物件非空
    if (command == null)
        throw new NullPointerException();
    // 獲取ctl的值
    int c = ctl.get();
    // 判斷如果當前工作執行緒數小於核心執行緒數,則建立新的核心執行緒並且執行傳入的任務
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            // 如果建立新的核心執行緒成功則直接返回
            return;
        // 這裡說明建立核心執行緒失敗,需要更新ctl的臨時變數c
        c = ctl.get();
    }
    // 走到這裡說明建立新的核心執行緒失敗,也就是當前工作執行緒數大於等於corePoolSize
    // 判斷執行緒池是否處於執行中狀態,同時嘗試用非阻塞方法向任務佇列放入任務(放入任務失敗返回false)
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        // 這裡是向任務佇列投放任務成功,對執行緒池的執行中狀態做二次檢查
        // 如果執行緒池二次檢查狀態是非執行中狀態,則從任務佇列移除當前的任務呼叫拒絕策略處理之(也就是移除前面成功入隊的任務例項)
        if (! isRunning(recheck) && remove(command))
            // 呼叫拒絕策略處理任務 - 返回
            reject(command);
        // 走到下面的else if分支,說明有以下的前提:
        // 0、待執行的任務已經成功加入任務佇列
        // 1、執行緒池可能是RUNNING狀態
        // 2、傳入的任務可能從任務佇列中移除失敗(移除失敗的唯一可能就是任務已經被執行了)
        // 如果當前工作執行緒數量為0,則建立一個非核心執行緒並且傳入的任務物件為null - 返回
        // 也就是建立的非核心執行緒不會馬上執行,而是等待獲取任務佇列的任務去執行 
        // 如果前工作執行緒數量不為0,原來應該是最後的else分支,但是可以什麼也不做,因為任務已經成功入佇列,總會有合適的時機分配其他空閒執行緒去執行它
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    // 走到這裡說明有以下的前提:
    // 0、執行緒池中的工作執行緒總數已經大於等於corePoolSize(簡單來說就是核心執行緒已經全部懶建立完畢)
    // 1、執行緒池可能不是RUNNING狀態
    // 2、執行緒池可能是RUNNING狀態同時任務佇列已經滿了
    // 如果向任務佇列投放任務失敗,則會嘗試建立非核心執行緒傳入任務執行
    // 建立非核心執行緒失敗,此時需要拒絕執行任務
    else if (!addWorker(command, false))
        // 呼叫拒絕策略處理任務 - 返回
        reject(command);
}

這裡簡單分析一下整個流程:

  1. 如果當前工作執行緒總數小於corePoolSize,則直接建立核心執行緒執行任務(任務例項會傳入直接用於構造工作執行緒例項)。
  2. 如果當前工作執行緒總數大於等於corePoolSize,判斷執行緒池是否處於執行中狀態,同時嘗試用非阻塞方法向任務佇列放入任務,這裡會二次檢查執行緒池執行狀態,如果當前工作執行緒數量為0,則建立一個非核心執行緒並且傳入的任務物件為null。
  3. 如果向任務佇列投放任務失敗(任務佇列已經滿了),則會嘗試建立非核心執行緒傳入任務例項執行。
  4. 如果建立非核心執行緒失敗,此時需要拒絕執行任務,呼叫拒絕策略處理任務。

這裡是一個疑惑點:為什麼需要二次檢查執行緒池的執行狀態,當前工作執行緒數量為0,嘗試建立一個非核心執行緒並且傳入的任務物件為null?這個可以看API註釋:

如果一個任務成功加入任務佇列,我們依然需要二次檢查是否需要新增一個工作執行緒(因為所有存活的工作執行緒有可能在最後一次檢查之後已經終結)或者執行當前方法的時候執行緒池是否已經shutdown了。所以我們需要二次檢查執行緒池的狀態,必須時把任務從任務佇列中移除或者在沒有可用的工作執行緒的前提下新建一個工作執行緒。

任務提交流程從呼叫者的角度來看如下:

j-u-c-t-p-e-3

addWorker方法原始碼分析

boolean addWorker(Runnable firstTask, boolean core)方法的第一的引數可以用於直接傳入任務例項,第二個引數用於標識將要建立的工作執行緒是否核心執行緒。方法原始碼如下:

// 新增工作執行緒,如果返回false說明沒有新建立工作執行緒,如果返回true說明建立和啟動工作執行緒成功
private boolean addWorker(Runnable firstTask, boolean core) {
    retry:  
    // 注意這是一個死迴圈 - 最外層迴圈
    for (int c = ctl.get();;) {
        // 這個是十分複雜的條件,這裡先拆分多個與(&&)條件:
        // 1. 執行緒池狀態至少為SHUTDOWN狀態,也就是rs >= SHUTDOWN(0)
        // 2. 執行緒池狀態至少為STOP狀態,也就是rs >= STOP(1),或者傳入的任務例項firstTask不為null,或者任務佇列為空
        // 其實這個判斷的邊界是執行緒池狀態為shutdown狀態下,不會再接受新的任務,在此前提下如果狀態已經到了STOP、或者傳入任務不為空、或者任務佇列為空(已經沒有積壓任務)都不需要新增新的執行緒
        if (runStateAtLeast(c, SHUTDOWN)
            && (runStateAtLeast(c, STOP)
                || firstTask != null
                || workQueue.isEmpty()))
            return false;
        // 注意這也是一個死迴圈 - 二層迴圈
        for (;;) {
            // 這裡每一輪迴圈都會重新獲取工作執行緒數wc
            // 1. 如果傳入的core為true,表示將要建立核心執行緒,通過wc和corePoolSize判斷,如果wc >= corePoolSize,則返回false表示建立核心執行緒失敗
            // 1. 如果傳入的core為false,表示將要創非建核心執行緒,通過wc和maximumPoolSize判斷,如果wc >= maximumPoolSize,則返回false表示建立非核心執行緒失敗
            if (workerCountOf(c)
                >= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK))
                return false;
            // 成功通過CAS更新工作執行緒數wc,則break到最外層的迴圈
            if (compareAndIncrementWorkerCount(c))
                break retry;
            // 走到這裡說明了通過CAS更新工作執行緒數wc失敗,這個時候需要重新判斷執行緒池的狀態是否由RUNNING已經變為SHUTDOWN
            c = ctl.get();  // Re-read ctl
            // 如果執行緒池狀態已經由RUNNING已經變為SHUTDOWN,則重新跳出到外層迴圈繼續執行
            if (runStateAtLeast(c, SHUTDOWN))
                continue retry;
            // 如果執行緒池狀態依然是RUNNING,CAS更新工作執行緒數wc失敗說明有可能是併發更新導致的失敗,則在內層迴圈重試即可 
            // else CAS failed due to workerCount change; retry inner loop 
        }
    }
    // 標記工作執行緒是否啟動成功
    boolean workerStarted = false;
    // 標記工作執行緒是否建立成功
    boolean workerAdded = false;
    Worker w = null;
    try {
        // 傳入任務例項firstTask建立Worker例項,Worker構造裡面會通過執行緒工廠建立新的Thread物件,所以下面可以直接操作Thread t = w.thread
        // 這一步Worker例項已經建立,但是沒有加入工作執行緒集合或者啟動它持有的執行緒Thread例項
        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 c = ctl.get();
                // 這裡主要在加鎖的前提下判斷ThreadFactory建立的執行緒是否存活或者判斷獲取鎖成功之後執行緒池狀態是否已經更變為SHUTDOWN
                // 1. 如果執行緒池狀態依然為RUNNING,則只需要判斷執行緒例項是否存活,需要新增到工作執行緒集合和啟動新的Worker
                // 2. 如果執行緒池狀態小於STOP,也就是RUNNING或者SHUTDOWN狀態下,同時傳入的任務例項firstTask為null,則需要新增到工作執行緒集合和啟動新的Worker
                // 對於2,換言之,如果執行緒池處於SHUTDOWN狀態下,同時傳入的任務例項firstTask不為null,則不會新增到工作執行緒集合和啟動新的Worker
                // 這一步其實有可能建立了新的Worker例項但是並不啟動(臨時物件,沒有任何強引用),這種Worker有可能成功下一輪GC被收集的垃圾物件
                if (isRunning(c) ||
                    (runStateLessThan(c, STOP) && 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;
                    // 這裡更新工作執行緒是否啟動成功標識為true,後面才會呼叫Thread#start()方法啟動真實的執行緒例項
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            // 如果成功新增工作執行緒,則呼叫Worker內部的執行緒例項t的Thread#start()方法啟動真實的執行緒例項
            if (workerAdded) {
                t.start();
                // 標記執行緒啟動成功
                workerStarted = true;
            }
        }
    } finally {
        // 執行緒啟動失敗,需要從工作執行緒集合移除對應的Worker
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

// 新增Worker失敗
private void addWorkerFailed(Worker w) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        // 從工作執行緒集合移除之
        if (w != null)
            workers.remove(w);
        // wc數量減1    
        decrementWorkerCount();
        // 基於狀態判斷嘗試終結執行緒池
        tryTerminate();
    } finally {
        mainLock.unlock();
    }
}

筆者發現了Doug Lea大神十分喜歡複雜的條件判斷,而且單行復雜判斷不喜歡加花括號,像下面這種程式碼在他編寫的很多類庫中都比較常見:

if (runStateAtLeast(c, SHUTDOWN)
    && (runStateAtLeast(c, STOP)
        || firstTask != null
        || workQueue.isEmpty()))
    return false;
// ....
//  程式碼拆分一下如下 
boolean atLeastShutdown = runStateAtLeast(c, SHUTDOWN);     # rs >= SHUTDOWN(0)
boolean atLeastStop = runStateAtLeast(c, STOP) || firstTask != null || workQueue.isEmpty();     
if (atLeastShutdown && atLeastStop){
   return false;
}

上面的分析邏輯中需要注意一點,Worker例項建立的同時,在其建構函式中會通過ThreadFactory建立一個Java執行緒Thread例項,後面會加鎖後二次檢查是否需要把Worker例項新增到工作執行緒集合workers中和是否需要啟動Worker中持有的Thread例項,只有啟動了Thread例項例項,Worker才真正開始運作,否則只是一個無用的臨時物件。Worker本身也實現了Runnable介面,它可以看成是一個Runnable的介面卡。

工作執行緒內部類Worker原始碼分析

執行緒池中的每一個具體的工作執行緒被包裝為內部類Worker例項,Worker繼承於AbstractQueuedSynchronizer(AQS),實現了Runnable介面:

private final class Worker extends AbstractQueuedSynchronizer implements Runnable{
    /**
        * This class will never be serialized, but we provide a
        * serialVersionUID to suppress a javac warning.
        */
    private static final long serialVersionUID = 6138294804551838833L;

    // 儲存ThreadFactory建立的執行緒例項,如果ThreadFactory建立執行緒失敗則為null
    final Thread thread;
    // 儲存傳入的Runnable任務例項
    Runnable firstTask;
    // 記錄每個執行緒完成的任務總數
    volatile long completedTasks;
    
    // 唯一的建構函式,傳入任務例項firstTask,注意可以為null
    Worker(Runnable firstTask) {
        // 禁止執行緒中斷,直到runWorker()方法執行
        setState(-1); // inhibit interrupts until runWorker
        this.firstTask = firstTask;
        // 通過ThreadFactory建立執行緒例項,注意一下Worker例項自身作為Runnable用於建立新的執行緒例項
        this.thread = getThreadFactory().newThread(this);
    }

    // 委託到外部的runWorker()方法,注意runWorker()方法是執行緒池的方法,而不是Worker的方法
    public void run() {
        runWorker(this);
    }

    // Lock methods
    //
    // The value 0 represents the unlocked state.
    // The value 1 represents the locked state.
    //  是否持有獨佔鎖,state值為1的時候表示持有鎖,state值為0的時候表示已經釋放鎖
    protected boolean isHeldExclusively() {
        return getState() != 0;
    }

    // 獨佔模式下嘗試獲取資源,這裡沒有判斷傳入的變數,直接CAS判斷0更新為1是否成功,成功則設定獨佔執行緒為當前執行緒
    protected boolean tryAcquire(int unused) {
        if (compareAndSetState(0, 1)) {
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false;
    }
    
    // 獨佔模式下嘗試是否資源,這裡沒有判斷傳入的變數,直接把state設定為0
    protected boolean tryRelease(int unused) {
        setExclusiveOwnerThread(null);
        setState(0);
        return true;
    }
    
    // 加鎖
    public void lock()        { acquire(1); }

    // 嘗試加鎖
    public boolean tryLock()  { return tryAcquire(1); }

    // 解鎖
    public void unlock()      { release(1); }

    // 是否鎖定
    public boolean isLocked() { return isHeldExclusively(); }
    
    // 啟動後進行執行緒中斷,注意這裡會判斷執行緒例項的中斷標誌位是否為false,只有中斷標誌位為false才會中斷
    void interruptIfStarted() {
        Thread t;
        if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
            try {
                t.interrupt();
            } catch (SecurityException ignore) {
            }
        }
    }
}

Worker的建構函式裡面的邏輯十分重要,通過ThreadFactory建立的Thread例項同時傳入Worker例項,因為Worker本身實現了Runnable,所以可以作為任務提交到執行緒中執行。只要Worker持有的執行緒例項w呼叫Thread#start()方法就能在合適時機執行Worker#run()。簡化一下邏輯如下:

// addWorker()方法中構造
Worker worker = createWorker();
// 通過執行緒池構造時候傳入
ThreadFactory threadFactory = getThreadFactory();
// Worker建構函式中
Thread thread = threadFactory.newThread(worker);
// addWorker()方法中啟動
thread.start();

Worker繼承自AQS,這裡使用了AQS的獨佔模式,這裡有個技巧是構造Worker的時候,把AQS的資源(狀態)通過setState(-1)設定為-1,這是因為Worker例項剛建立時AQSstate的預設值為0,此時執行緒尚未啟動,不能在這個時候進行執行緒中斷,見Worker#interruptIfStarted()方法。Worker中兩個覆蓋AQS的方法tryAcquire()tryRelease()都沒有判斷外部傳入的變數,前者直接CAS(0,1),後者直接setState(0)。接著看核心方法ThreadPoolExecutor#runWorker()

final void runWorker(Worker w) {
    // 獲取當前執行緒,實際上和Worker持有的執行緒例項是相同的
    Thread wt = Thread.currentThread();
    // 獲取Worker中持有的初始化時傳入的任務物件,這裡注意存放在臨時變數task中
    Runnable task = w.firstTask;
    // 設定Worker中持有的初始化時傳入的任務物件為null
    w.firstTask = null;
    // 由於Worker初始化時AQS中state設定為-1,這裡要先做一次解鎖把state更新為0,允許執行緒中斷
    w.unlock(); // allow interrupts
    // 記錄執行緒是否因為使用者異常終結,預設是true
    boolean completedAbruptly = true;
    try {
        // 初始化任務物件不為null,或者從任務佇列獲取任務不為空(從任務佇列獲取到的任務會更新到臨時變數task中)
        // getTask()由於使用了阻塞佇列,這個while迴圈如果命中後半段會處於阻塞或者超時阻塞狀態,getTask()返回為null會導致執行緒跳出死迴圈使執行緒終結
        while (task != null || (task = getTask()) != null) {
            // Worker加鎖,本質是AQS獲取資源並且嘗試CAS更新state由0更變為1
            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
            // 如果執行緒池正在停止(也就是由RUNNING或者SHUTDOWN狀態向STOP狀態變更),那麼要確保當前工作執行緒是中斷狀態
            // 否則,要保證當前執行緒不是中斷狀態
            if ((runStateAtLeast(ctl.get(), STOP) ||
                    (Thread.interrupted() &&
                    runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt();
            try {
                // 鉤子方法,任務執行前
                beforeExecute(wt, task);
                try {
                    task.run();
                    // 鉤子方法,任務執行後 - 正常情況
                    afterExecute(task, null);
                } catch (Throwable ex) {
                    // 鉤子方法,任務執行後 - 異常情況
                    afterExecute(task, ex);
                    throw ex;
                }
            } finally {
                // 清空task臨時變數,這個很重要,否則while會死迴圈執行同一個task
                task = null;
                // 累加Worker完成的任務數
                w.completedTasks++;
                // Worker解鎖,本質是AQS釋放資源,設定state為0
                w.unlock();
            }
        }
        // 走到這裡說明某一次getTask()返回為null,執行緒正常退出
        completedAbruptly = false;
    } finally {
        // 處理執行緒退出,completedAbruptly為true說明由於使用者異常導致執行緒非正常退出
        processWorkerExit(w, completedAbruptly);
    }
}

這裡重點拆解分析一下判斷當前工作執行緒中斷狀態的程式碼:

if ((runStateAtLeast(ctl.get(), STOP) ||
        (Thread.interrupted() &&
        runStateAtLeast(ctl.get(), STOP))) &&
    !wt.isInterrupted())
    wt.interrupt();
// 先簡化一下判斷邏輯,如下
// 判斷執行緒池狀態是否至少為STOP,rs >= STOP(1)
boolean atLeastStop = runStateAtLeast(ctl.get(), STOP);
// 判斷執行緒池狀態是否至少為STOP,同時判斷當前執行緒的中斷狀態並且清空當前執行緒的中斷狀態
boolean interruptedAndAtLeastStop = Thread.interrupted() && runStateAtLeast(ctl.get(), STOP);
if (atLeastStop || interruptedAndAtLeastStop && !wt.isInterrupted()){
    wt.interrupt();
}

Thread.interrupted()方法獲取執行緒的中斷狀態同時會清空該中斷狀態,這裡之所以會呼叫這個方法是因為在執行上面這個if邏輯同時外部有可能呼叫shutdownNow()方法,shutdownNow()方法中也存在中斷所有Worker執行緒的邏輯,但是由於shutdownNow()方法中會遍歷所有Worker做執行緒中斷,有可能無法及時在任務提交到Worker執行之前進行中斷,所以這個中斷邏輯會在Worker內部執行,就是if程式碼塊的邏輯。這裡還要注意的是:STOP狀態下會拒絕所有新提交的任務,不會再執行任務佇列中的任務,同時會中斷所有Worker執行緒。也就是,即使任務Runnable已經runWorker()中前半段邏輯取出,只要還沒走到呼叫其Runnable#run(),都有可能被中斷。假設剛好發生了進入if程式碼塊的邏輯同時外部呼叫了shutdownNow()方法,那麼if邏輯內會判斷執行緒中斷狀態並且重置,那麼shutdownNow()方法中呼叫的interruptWorkers()就不會因為中斷狀態判斷出現問題導致二次中斷執行緒(會導致異常)。

小結一下上面runWorker()方法的核心流程:

  1. Worker先執行一次解鎖操作,用於解除不可中斷狀態。
  2. 通過while迴圈呼叫getTask()方法從任務佇列中獲取任務(當然,首輪迴圈也有可能是外部傳入的firstTask任務例項)。
  3. 如果執行緒池更變為STOP狀態,則需要確保工作執行緒是中斷狀態並且進行中斷處理,否則要保證工作執行緒必須不是中斷狀態。
  4. 執行任務例項Runnale#run()方法,任務例項執行之前和之後(包括正常執行完畢和異常執行情況)分別會呼叫鉤子方法beforeExecute()afterExecute()
  5. while迴圈跳出意味著runWorker()方法結束和工作執行緒生命週期結束(Worker#run()生命週期完結),會呼叫processWorkerExit()處理工作執行緒退出的後續工作。

j-u-c-t-p-e-4

接下來分析一下從任務佇列中獲取任務的getTask()方法和處理執行緒退出的後續工作的方法processWorkerExit()

getTask方法原始碼分析

getTask()方法是工作執行緒在while死迴圈中獲取任務佇列中的任務物件的方法:

private Runnable getTask() {
    // 記錄上一次從佇列中拉取的時候是否超時
    boolean timedOut = false; // Did the last poll() time out?
    // 注意這是死迴圈
    for (;;) {
        int c = ctl.get();

        // Check if queue empty only if necessary.
        // 第一個if:如果執行緒池狀態至少為SHUTDOWN,也就是rs >= SHUTDOWN(0),則需要判斷兩種情況(或邏輯):
        // 1. 執行緒池狀態至少為STOP(1),也就是執行緒池正在停止,一般是呼叫了shutdownNow()方法
        // 2. 任務佇列為空
        // 如果線上程池至少為SHUTDOWN狀態並且滿足上面兩個條件之一,則工作執行緒數wc減去1,然後直接返回null
        if (runStateAtLeast(c, SHUTDOWN)
            && (runStateAtLeast(c, STOP) || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }
        // 跑到這裡說明執行緒池還處於RUNNING狀態,重新獲取一次工作執行緒數
        int wc = workerCountOf(c);

        // Are workers subject to culling?
        // timed臨時變數勇於執行緒超時控制,決定是否需要通過poll()此帶超時的非阻塞方法進行任務佇列的任務拉取
        // 1.allowCoreThreadTimeOut預設值為false,如果設定為true,則允許核心執行緒也能通過poll()方法從任務佇列中拉取任務
        // 2.工作執行緒數大於核心執行緒數的時候,說明執行緒池中建立了額外的非核心執行緒,這些非核心執行緒一定是通過poll()方法從任務佇列中拉取任務
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
        // 第二個if:
        // 1.wc > maximumPoolSize說明當前的工作執行緒總數大於maximumPoolSize,說明了通過setMaximumPoolSize()方法減少了執行緒池容量
        // 或者 2.timed && timedOut說明了執行緒命中了超時控制並且上一輪迴圈通過poll()方法從任務佇列中拉取任務為null
        // 並且 3. 工作執行緒總數大於1或者任務佇列為空,則通過CAS把執行緒數減去1,同時返回null,
        // CAS把執行緒數減去1失敗會進入下一輪迴圈做重試
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }

        try {
            // 如果timed為true,通過poll()方法做超時拉取,keepAliveTime時間內沒有等待到有效的任務,則返回null
            // 如果timed為false,通過take()做阻塞拉取,會阻塞到有下一個有效的任務時候再返回(一般不會是null)
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            // 這裡很重要,只有非null時候才返回,null的情況下會進入下一輪迴圈
            if (r != null)
                return r;
            // 跑到這裡說明上一次從任務佇列中獲取到的任務為null,一般是workQueue.poll()方法超時返回null
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

這個方法中,有兩處十分龐大的if邏輯,對於第一處if可能導致工作執行緒數減去1直接返回null的場景有:

  1. 執行緒池狀態為SHUTDOWN,一般是呼叫了shutdown()方法,並且任務佇列為空。
  2. 執行緒池狀態為STOP

對於第二處if,邏輯有點複雜,先拆解一下:

// 工作執行緒總數大於maximumPoolSize,說明了通過setMaximumPoolSize()方法減少了執行緒池容量
boolean b1 = wc > maximumPoolSize;
// 允許執行緒超時同時上一輪通過poll()方法從任務佇列中拉取任務為null
boolean b2 = timed && timedOut;
// 工作執行緒總數大於1
boolean b3 = wc > 1;
// 任務佇列為空
boolean b4 = workQueue.isEmpty();
boolean r = (b1 || b2) && (b3 || b4);
if (r) {
    if (compareAndDecrementWorkerCount(c)){
        return null;
    }else{
        continue;
    }
}

這段邏輯大多數情況下是針對非核心執行緒。在execute()方法中,當執行緒池總數已經超過了corePoolSize並且還小於maximumPoolSize時,當任務佇列已經滿了的時候,會通過addWorker(task,false)新增非核心執行緒。而這裡的邏輯恰好類似於addWorker(task,false)的反向操作,用於減少非核心執行緒,使得工作執行緒總數趨向於corePoolSize。如果對於非核心執行緒,上一輪迴圈獲取任務物件為null,這一輪迴圈很容易滿足timed && timedOut為true,這個時候getTask()返回null會導致Worker#runWorker()方法跳出死迴圈,之後執行processWorkerExit()方法處理後續工作,而該非核心執行緒對應的Worker則變成“遊離物件”,等待被JVM回收。當allowCoreThreadTimeOut設定為true的時候,這裡分析的非核心執行緒的生命週期終結邏輯同時會適用於核心執行緒。那麼可以總結出keepAliveTime的意義:

  • 當允許核心執行緒超時,也就是allowCoreThreadTimeOut設定為true的時候,此時keepAliveTime表示空閒的工作執行緒的存活週期。
  • 預設情況下不允許核心執行緒超時,此時keepAliveTime表示空閒的非核心執行緒的存活週期。

在一些特定的場景下,配置合理的keepAliveTime能夠更好地利用執行緒池的工作執行緒資源。

processWorkerExit方法原始碼分析

processWorkerExit()方法是為將要終結的Worker做一次清理和資料記錄工作(因為processWorkerExit()方法也包裹在runWorker()方法finally程式碼塊中,其實工作執行緒在執行完processWorkerExit()方法才算真正的終結)。

private void processWorkerExit(Worker w, boolean completedAbruptly) {
    // 因為丟擲使用者異常導致執行緒終結,直接使工作執行緒數減1即可
    // 如果沒有任何異常丟擲的情況下是通過getTask()返回null引導執行緒正常跳出runWorker()方法的while死迴圈從而正常終結,這種情況下,在getTask()中已經把執行緒數減1
    if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
        decrementWorkerCount();

    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        // 全域性的已完成任務記錄數加上此將要終結的Worker中的已完成任務數
        completedTaskCount += w.completedTasks;
        // 工作執行緒集合中移除此將要終結的Worker
        workers.remove(w);
    } finally {
        mainLock.unlock();
    }
     
    // 見下一小節分析,用於根據當前執行緒池的狀態判斷是否需要進行執行緒池terminate處理
    tryTerminate();

    int c = ctl.get();
    // 如果執行緒池的狀態小於STOP,也就是處於RUNNING或者SHUTDOWN狀態的前提下:
    // 1.如果執行緒不是由於丟擲使用者異常終結,如果允許核心執行緒超時,則保持執行緒池中至少存在一個工作執行緒
    // 2.如果執行緒由於丟擲使用者異常終結,或者當前工作執行緒數,那麼直接新增一個新的非核心執行緒
    if (runStateLessThan(c, STOP)) {
        if (!completedAbruptly) {
            // 如果允許核心執行緒超時,最小值為0,否則為corePoolSize
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            // 如果最小值為0,同時任務佇列不空,則更新最小值為1
            if (min == 0 && ! workQueue.isEmpty())
                min = 1;
            // 工作執行緒數大於等於最小值,直接返回不新增非核心執行緒
            if (workerCountOf(c) >= min)
                return; // replacement not needed
        }
        addWorker(null, false);
    }
}

程式碼的後面部分割槽域,會判斷執行緒池的狀態,如果執行緒池是RUNNING或者SHUTDOWN狀態的前提下,如果當前的工作執行緒由於丟擲使用者異常被終結,那麼會新建立一個非核心執行緒。如果當前的工作執行緒並不是丟擲使用者異常被終結(正常情況下的終結),那麼會這樣處理:

  • allowCoreThreadTimeOut為true,也就是允許核心執行緒超時的前提下,如果任務佇列空,則會通過建立一個非核心執行緒保持執行緒池中至少有一個工作執行緒。
  • allowCoreThreadTimeOut為false,如果工作執行緒總數大於corePoolSize則直接返回,否則建立一個非核心執行緒,也就是會趨向於保持執行緒池中的工作執行緒數量趨向於corePoolSize

processWorkerExit()執行完畢之後,意味著該工作執行緒的生命週期已經完結。

tryTerminate方法原始碼分析

每個工作執行緒終結的時候都會呼叫tryTerminate()方法:

final void tryTerminate() {
    for (;;) {
        int c = ctl.get();
        // 判斷執行緒池的狀態,如果是下面三種情況下的任意一種則直接返回:
        // 1.執行緒池處於RUNNING狀態
        // 2.執行緒池至少為TIDYING狀態,也就是TIDYING或者TERMINATED狀態,意味著已經走到了下面的步驟,執行緒池即將終結
        // 3.執行緒池至少為STOP狀態並且任務佇列不為空
        if (isRunning(c) ||
            runStateAtLeast(c, TIDYING) ||
            (runStateLessThan(c, STOP) && ! workQueue.isEmpty()))
            return;
        // 工作執行緒數不為0,則中斷工作執行緒集合中的第一個空閒的工作執行緒
        if (workerCountOf(c) != 0) { // Eligible to terminate
            interruptIdleWorkers(ONLY_ONE);
            return;
        }

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            // CAS設定執行緒池狀態為TIDYING,如果設定成功則執行鉤子方法terminated()
            if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                try {
                    terminated();
                } finally {
                    // 最後更新執行緒池狀態為TERMINATED
                    ctl.set(ctlOf(TERMINATED, 0));
                    // 喚醒阻塞在termination條件的所有執行緒,這個變數的await()方法在awaitTermination()中呼叫
                    termination.signalAll();
                }
                return;
            }
        } finally {
            mainLock.unlock();
        }
        // else retry on failed CAS
    }
}

// 中斷空閒的工作執行緒,onlyOne為true的時候,只會中斷工作執行緒集合中的某一個執行緒
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();
    }
}

這裡有疑惑的地方是tryTerminate()方法的第二個if程式碼邏輯:工作執行緒數不為0,則中斷工作執行緒集合中的第一個空閒的工作執行緒。方法API註釋中有這樣一段話:

If otherwise eligible to terminate but workerCount is nonzero, interrupts an idle worker to ensure that shutdown signals propagate.
當滿足終結執行緒池的條件但是工作執行緒數不為0,這個時候需要中斷一個空閒的工作執行緒去確保執行緒池關閉的訊號得以傳播。

下面將會分析的shutdown()方法中會通過interruptIdleWorkers()中斷所有的空閒執行緒,這個時候有可能有非空閒的執行緒在執行某個任務,執行任務完畢之後,如果它剛好是核心執行緒,就會在下一輪迴圈阻塞在任務佇列的take()方法,如果不做額外的干預,它甚至會線上程池關閉之後永久阻塞在任務佇列的take()方法中。為了避免這種情況,每個工作執行緒退出的時候都會嘗試中斷工作執行緒集合中的某一個空閒的執行緒,確保所有空閒的執行緒都能夠正常退出。

interruptIdleWorkers()方法中會對每一個工作執行緒先進行tryLock()判斷,只有返回true才有可能進行執行緒中斷。我們知道runWorker()方法中,工作執行緒在每次從任務佇列中獲取到非null的任務之後,會先進行加鎖Worker#lock()操作,這樣就能避免執行緒在執行任務的過程中被中斷,保證被中斷的一定是空閒的工作執行緒。

shutdown方法原始碼分析

執行緒池關閉操作有幾個相關的變體方法,先看shutdown()

public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        // 許可權校驗,安全策略相關判斷
        checkShutdownAccess();
        // 設定SHUTDOWN狀態
        advanceRunState(SHUTDOWN);
        // 中斷所有的空閒的工作執行緒
        interruptIdleWorkers();
        // 鉤子方法
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
        mainLock.unlock();
    }
    // 呼叫上面分析果敢的嘗試terminate方法,使狀態更變為TIDYING,執行鉤子方法terminated()後,最終狀態更新為TERMINATED
    tryTerminate();
}

// 升提狀態
private void advanceRunState(int targetState) {
    // assert targetState == SHUTDOWN || targetState == STOP;
    for (;;) {
        int c = ctl.get();
        // 執行緒池狀態至少為targetState或者CAS設定狀態為targetState則跳出迴圈
        if (runStateAtLeast(c, targetState) ||
            ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))
            break;
    }
}

// 中斷所有的空閒的工作執行緒
private void interruptIdleWorkers() {
    interruptIdleWorkers(false);
}

接著看shutdownNow()方法:

public List<Runnable> shutdownNow() {
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        // 許可權校驗,安全策略相關判斷
        checkShutdownAccess();
        // 設定STOP狀態
        advanceRunState(STOP);
        // 中斷所有的工作執行緒
        interruptWorkers();
        // 清空工作佇列並且取出所有的未執行的任務
        tasks = drainQueue();
    } finally {
        mainLock.unlock();
    }
     // 呼叫上面分析果敢的嘗試terminate方法,使狀態更變為TIDYING,執行鉤子方法terminated()後,最終狀態更新為TERMINATED
    tryTerminate();
    return tasks;
}

// 遍歷所有的工作執行緒,如果state > 0(啟動狀態)則進行中斷
private void interruptWorkers() {
    // assert mainLock.isHeldByCurrentThread();
    for (Worker w : workers)
        w.interruptIfStarted();
}

shutdownNow()方法會把執行緒池狀態先更變為STOP,中斷所有的工作執行緒(AbstractQueuedSynchronizerstate值大於0的Worker例項,也就是包括正在執行任務的Worker和空閒的Worker),然後遍歷任務佇列,取出(移除)所有任務存放在一個列表中返回。

最後看awaitTermination()方法:

public boolean awaitTermination(long timeout, TimeUnit unit)
    throws InterruptedException {
    // 轉換timeout的單位為納秒
    long nanos = unit.toNanos(timeout);
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        // 迴圈等待直到執行緒池狀態更變為TERMINATED,每輪迴圈等待nanos納秒
        while (runStateLessThan(ctl.get(), TERMINATED)) {
            if (nanos <= 0L)
                return false;
            nanos = termination.awaitNanos(nanos);
        }
        return true;
    } finally {
        mainLock.unlock();
    }
}

awaitTermination()雖然不是shutdown()方法體系,但是它的處理邏輯就是確保呼叫此方法的執行緒會阻塞到tryTerminate()方法成功把執行緒池狀態更新為TERMINATED後再返回,可以使用在某些需要感知執行緒池終結時刻的場景。

有一點值得關注的是:shutdown()方法只會中斷空閒的工作執行緒,如果工作執行緒正在執行任務物件Runnable#run(),這種情況下的工作執行緒不會中斷,而是等待下一輪執行getTask()方法的時候通過執行緒池狀態判斷正常終結該工作執行緒。

理解可重入鎖mainLock成員變數

private final ReentrantLock mainLock = new ReentrantLock();
private final Condition termination = mainLock.newCondition();

先看了ThreadPoolExecutor內部成員屬性mainLock的引用情況:

歸結一下mainLock的使用場景:

方法 主要作用
tryTerminate 保證狀態TIDYING -> TERMINATED,鉤子方法terminated()回撥和條件變數喚醒
interruptIdleWorkers 保護工作執行緒中斷的序列化,避免"中斷風暴"
addWorker 保護工作執行緒集合避免併發增加工作執行緒、保護度量統計資料變更
processWorkerExit 保護度量統計資料變更
shutdownshutdownNowawaitTermination 見下文分析
getPoolSizegetActiveCountgetLargestPoolSizegetTaskCountgetCompletedTaskCount 保護度量統計資料讀取,這些統計資料來一般源於Worker集合的屬性統計

這裡分析一下執行緒池如何通過可重入鎖和條件變數實現相對優雅地關閉。先看shutdown()方法:

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

這裡shutdown()中除了tryTerminate(),其他它方法都是包裹在鎖裡面執行,確保工作執行緒集合穩定性以及關閉許可權、確保狀態變更序列化,中斷所有工作執行緒並且避免工作執行緒"中斷風暴"(多次併發呼叫shutdown()如果不加鎖,會反覆中斷工作執行緒)。

public List<Runnable> shutdownNow() {
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        advanceRunState(STOP);
        interruptWorkers();
        tasks = drainQueue();  # <---  多了這一步
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
    return tasks;
}

shutdownNow()方法其實加鎖的目的和shutdown()差不多,不過多了一步:匯出任務佇列中的剩餘的任務例項列表。awaitTermination()方法中使用到前面提到過的條件變數termination

// 條件變數必須在鎖程式碼塊中執行,和synchronized關鍵字用法差不多
public boolean awaitTermination(long timeout, TimeUnit unit)
    throws InterruptedException {
    long nanos = unit.toNanos(timeout);
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        // 死迴圈確保等待執行和狀態變更為TERMINATED
        while (runStateLessThan(ctl.get(), TERMINATED)) {
            if (nanos <= 0L)
                return false;
            nanos = termination.awaitNanos(nanos);   # <-- 確保當前呼叫執行緒阻塞等待對應的時間或者執行緒池狀態變更為TERMINATED,再退出等待
        }
        return true;
    } finally {
        mainLock.unlock();
    }
}

awaitTermination()方法的核心功能是:確保當前呼叫awaitTermination()方法的執行緒阻塞等待對應的時間或者執行緒池狀態變更為TERMINATED,再退出等待返回結果,這樣能夠讓使用者輸入一個可以接受的等待時間進行阻塞等待,或者執行緒池在其他執行緒中被呼叫了shutdown()方法狀態變更為TERMINATED就能正常解除阻塞。awaitTermination()方法的返回值為布林值,true代表執行緒池狀態變更為TERMINATED或者等待了輸入時間範圍內的時間週期被喚醒,意味則執行緒池正常退出,結果為false代表等待了超過輸入時間範圍內的時間週期,執行緒池的狀態依然沒有更變為TERMINATED

執行緒池中的工作執行緒如何優雅地退出,不導致當前任務執行丟失、任務狀態異常或者任務持有的資料異常,是一個很值得探討的專題,以後有機會一定會分析一下這個專題。

reject方法原始碼分析

reject(Runnable command)方法很簡單:

final void reject(Runnable command) {
    handler.rejectedExecution(command, this);
}

呼叫執行緒池持有的成員RejectedExecutionHandler例項回撥任務例項和當前執行緒池例項。

鉤子方法分析

JDK11為止,ThreadPoolExecutor提供的鉤子方法沒有增加,有以下幾個:

  • beforeExecute(Thread t, Runnable r):任務物件Runnable#run()執行之前觸發回撥。
  • afterExecute(Runnable r, Throwable t):任務物件Runnable#run()執行之後(包括異常完成情況和正常完成情況)觸發回撥。
  • terminated():執行緒池關閉的時候,狀態更變為TIDYING成功之後會回撥此方法,執行此方法完畢後,執行緒池狀態會更新為TERMINATED
  • onShutdown()shutdown()方法執行時候會回撥此方法,API註釋中提到此方法主要提供給ScheduledThreadPoolExecutor使用。

其中onShutdown()的方法修飾符為default,其他三個方法的修飾符為protected,必要時候可以自行擴充套件這些方法,可以實現監控、基於特定時機觸發具體操作等等。

其他方法

執行緒池本身提供了大量資料統計相關的方法、擴容方法、預建立方法等等,這些方法的原始碼並不複雜,這裡不做展開分析。

核心執行緒相關:

  • getCorePoolSize():獲取核心執行緒數。
  • setCorePoolSize():重新設定執行緒池的核心執行緒數。
  • prestartCoreThread():預啟動一個核心執行緒,當且僅當工作執行緒數量小於核心執行緒數量。
  • prestartAllCoreThreads():預啟動所有核心執行緒。

執行緒池容量相關:

  • getMaximumPoolSize():獲取執行緒池容量。
  • setMaximumPoolSize():重新設定執行緒池的最大容量。

執行緒存活週期相關:

  • setKeepAliveTime():設定空閒工作執行緒的存活週期。
  • getKeepAliveTime():獲取空閒工作執行緒的存活週期。

其他監控統計相關方法:

  • getTaskCount():獲取所有已經被執行的任務總數的近似值。
  • getCompletedTaskCount():獲取所有已經執行完成的任務總數的近似值。
  • getLargestPoolSize():獲取執行緒池的峰值執行緒數(最大池容量)。
  • getActiveCount():獲取所有活躍執行緒總數(正在執行任務的工作執行緒)的近似值。
  • getPoolSize():獲取工作執行緒集合的容量(當前執行緒池中的總工作執行緒數)。

任務佇列操作相關方法:

  • purge():移除任務佇列中所有是Future型別並且已經處於Cancelled狀態的任務。
  • remove():從任務佇列中移除指定的任務。
  • BlockingQueue<Runnable> getQueue():獲取任務佇列的引用。

有部分屬性值的設定有可能影響到執行緒池中的狀態或者工作執行緒的增減等,例如核心執行緒數改變,有可能會直接增減Worker,這裡就以ThreadPoolExecutor#setCorePoolSize()為例:

// 設定核心執行緒數量
public void setCorePoolSize(int corePoolSize) {
    // 輸入值不能小於0或者大於執行緒池的容量
    if (corePoolSize < 0 || maximumPoolSize < corePoolSize)
        throw new IllegalArgumentException();
    // delta = 傳入核心執行緒數和現存的核心執行緒數的差值
    int delta = corePoolSize - this.corePoolSize;
    this.corePoolSize = corePoolSize;
    // 如果當前執行緒池工作執行緒的總量大於傳入核心執行緒數,則中斷所有的工作執行緒
    if (workerCountOf(ctl.get()) > corePoolSize)
        interruptIdleWorkers();
    else if (delta > 0) {
        // 傳入核心執行緒數和現存的核心執行緒數的差值大於0,也就是核心執行緒擴容
        // 計算傳入核心執行緒數和現存的核心執行緒數的差值和任務佇列中任務個數的最小值,並且新增這個最小值個數的工作執行緒池
        // 任務佇列為空的情況下,k === 0,此時第一個條件 k--> 0就不滿足,不會進入迴圈,那麼這delta個需要建立的工作執行緒應該是在提交新任務的時候懶建立
        int k = Math.min(delta, workQueue.size());
        while (k-- > 0 && addWorker(null, true)) {
            // 如果任務佇列為空,則跳出迴圈
            if (workQueue.isEmpty())
                break;
        }
    }
}

這裡else if (delta > 0)後面的程式碼塊中有一段描述,翻譯一下:我們並不知道真正情況下"需要"多少新的工作執行緒。作為一種啟發式處理方式,預先啟動足夠多的新的工作執行緒(直到數量為核心執行緒池大小)來處理佇列中當前的任務,但如果在這樣做時佇列變為空,則停止建立新的工作執行緒。

小結

本文花大量功夫基於每一行程式碼分析JUC執行緒池ThreadPoolExecutor的核心方法execute()的實現,這個方法是整個執行緒池相關體系的基石,有了它才能擴充套件出帶回撥的非同步執行和基於時間進行任務排程的功能,後面將會編寫兩篇文章分別詳細分析執行緒池擴充套件服務ExecutorService的功能原始碼實現以及排程執行緒池ScheduledThreadPoolExecutor的原始碼實現,預計要耗時2-3周。

個人部落格原文:https://throwx.cn/2020/08/23/java-concurrency-thread-pool-executor/

相關文章