詳解 JUC 執行緒池中的 ThreadPoolExecutor

巔峰大詞典發表於2020-08-10

希望美好的東西能夠美好地終結,是一種卑微的人之常情。

image

前提

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

public interface Executor {
    void execute(Runnable command);
}

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

ThreadPoolExecutor的原理

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

JUC同步器框架

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

  • 全域性鎖mainLock成員屬性,是可重入鎖ReentrantLock型別,主要是用於訪問工作執行緒Worker集合和進行資料統計記錄時候的加鎖操作。
  • 條件變數terminationCondition型別,主要用於執行緒進行等待終結awaitTermination()方法時的帶期限阻塞。
  • 任務佇列workQueueBlockingQueue型別,任務佇列,用於存放待執行的任務。
  • 工作執行緒,內部類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型別,等待佇列或者叫任務佇列。
  • threadFactoryThreadFactory型別,執行緒工廠,用於建立工作執行緒(包括核心執行緒和非核心執行緒),預設使用Executors.defaultThreadFactory()作為內建執行緒工廠例項,一般自定義執行緒工廠才能更好地跟蹤工作執行緒。
  • handler
  • RejectedExecutionHandler

    型別,執行緒池的拒絕執行處理器,更多時候稱為拒絕策略,拒絕策略執行的時機是當阻塞佇列已滿、沒有空閒的執行緒(包括核心執行緒和非核心執行緒)並且繼續提交任務。提供了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位,它的位檢視如下:
image
然後就是執行緒池的狀態常量,這裡只詳細分析其中一個,其他類同,這裡看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)做一次與運算即可。

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

工作執行緒數為0的前提下: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;
}

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

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了。所以我們需要二次檢查執行緒池的狀態,必須時把任務從任務佇列中移除或者在沒有可用的工作執行緒的前提下新建一個工作執行緒。

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

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()處理工作執行緒退出的後續工作。

image

相關文章