Java8源碼閱讀 - 線程池ThreadPoolExecutor

閱讀源碼前需要大概了解的基礎知識
Java8 源碼閱讀 - AbstractQueuedSynchronizer
Java8源碼閱讀 - FutureTask
Java8源碼閱讀 - Executor、ExecutorService、ExecutorCompletionService

特性

線程池核心所解決的問題就是提供了一種綁定和資源管理的方式,通過控制一些參數達到優化執行每個任務的開銷,通常在執行大量異步任務時有明顯的性能提升,線程池還維護了一些基本的統計信息,幫助來了解池中的基本狀態;

ThreadPoolExecutor提供了許多可調參數和可擴展的鉤子來應對在不同場景下的廣泛應用;

  • corePoolSize和maximumPoolSize
    當在方法execute中提交一個新任務,并且運行的線程小于corePoolSize時,即使其他工作線程處于空閑狀態,將創建一個新線程來處理請求新的任務。如果有超過corePoolSize但小于maximumPoolSize的線程在運行,則只有在工作隊列已滿時才會創建新線程。通過將corePoolSizemaximumPoolSize設置為相同達到創建一個固定大小的線程池。通過將maximumPoolSize設置為一個沒有上限的值,例如Integer.MAX_VALUE,則允許池容納任意數量的并發任務。corePoolSizemaximumPoolSize僅在構建時設置,但也可以使用setCorePoolSizesetMaximumPoolSize動態更改它們;

  • 默認情況下,即使是核心線程也只是在新任務到達時才被創建和啟動,但是可以使用方法prestartCoreThreadprestartAllCoreThreads動態地覆蓋這一點,如果使用非空隊列構造池,則可能需要預啟動線程。

  • 新線程是使用ThreadFactory創建的。如果沒有另外指定,默認是Executors#defaultThreadFactory,它創建的線程都位于相同的線程組中,并且具有相同的優先級和非守護狀態。通過提供不同的ThreadFactory,可以更改線程的名稱、線程組、優先級、守護進程狀態等。如果ThreadFactory#newThread返回null則表示創建線程失敗,執行程序可能將繼續執行,但可能無法執行任何任務。

  • Keep Alive Time
    如果當前池中有超過corePoolSize的線程,那么如果空閑時間超過keepAliveTime,多余的線程將被終止。這提供了一種在線程池中未被積極使用時減少資源消耗的方法。如果線程池未來變得更加活躍時就會構造新的線程。默認情況下,該策略僅適用于擁有多于corePoolSize線程的情況。

  • Queuing
    使用BlockingQueue來傳輸和保存提交的任務。這個隊列中如果運行的線程小于corePoolSize,遇到新任務則總是希望添加新線程而不是排隊。如果正在運行的任務大于corePoolSize,執行程序總是希望對請求進行排隊,而不是添加新線程。如果不能對請求進行排隊,將創建一個新線程,除非該線程超過maximumPoolSize,在這種情況下,任務將被拒絕。

  • 無界隊列
    當所有corePoolSize大小的線程都處于繁忙狀態時,使用無界隊列,例如沒有定義大小的LinkedBlockingQueue,將導致新任務在隊列中等待。創建線程不會超過corePoolSize的大小,因此設置maximumPoolSize的值不會有任何效果。當每個任務完全獨立于其他任務時,無界隊列的用法可能是合適的,因為排隊任務不會影響其他任務的執行;例如在web頁面服務器中,盡管這種類型的排隊在平滑瞬時請求爆發的狀況下很有用,但在任務持續以快于處理任務的速度到達時,可能會出現無限制的工作隊列增長。

  • 有界隊列
    有界隊列(例如ArrayBlockingQueue)在使用有限的maximumPoolSize時有助于防止資源耗盡,但是調優和控制可能更困難。隊列大小和最大池的大小可以相互交換,使用大隊列和容量小的池可以最小化CPU的使用、操作系統資源和上下文切換開銷,但是可能會導致更低的吞吐量。如果任務經常阻塞(例如大量I/O等待場景),系統可能會為比設置所允許的更多線程安排時間。使用小隊列通常需要更大容量的池子,這會讓cpu更忙,也會降低吞吐量。

  • 拒絕任務
    當池子或者使用有限的工作隊列達到飽和時,我們提供的任務會返回RejectedExecutionHandler異常,ThreadPoolExecutor提供4個內置處理策略;分別是AbortPolicyCallerRunsPolicyDiscardPolicyDiscardOldestPolicy,當然也是允許自己擴展實現;

  • 鉤子函數
    ThreadPoolExecutor提供了任務執行前beforeExecute 和任務執行后afterExecute的鉤子,當然還有terminated來執行線程池關閉的一些特殊處理;

  • Finalization
    如果程序不再引用線程池,那么線程池可能會自動被關閉,如果想要確保忘記調用關閉也能回收未引用的池子,可以通過設置keepAliveTime、使用allowCoreThreadTimeOut或者設置0個核心線程corePoolSize

細節分析

構造器
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;
}

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

構造函數設置一些初始的設置項,相關參數的含義上面都有,而平常使用較多的Executors.newFixedThreadPool,默認使用的是無界隊列LinkedBlockingQueue

線程池狀態

線程池狀態由一個原子整數ctl儲存,包括了以下兩個含義

  1. workerCount:表示有效線程運行的數目
  2. runState:表示線程池的運行狀態
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

為了將兩個狀態塞進一個int類型,將workerCount限制為上限是229-1(大約5億)個線程,而不是231-1個;

runState的主要生命周期有以下幾個:

  • RUNNING:運行中,允許接受新任務并處理排隊的任務;
  • SHUTDOWN:不接受新任務,但處理排隊的任務;
  • STOP:不接受新任務、不處理排隊的任務和嘗試中斷正在進行的任務;
  • TIDYING:所有任務都已終止,workerCount為0,過渡到TIDYING狀態并將運行terminate()鉤子方法;
  • TERMINATED:當terminate()方法運行完成;
// 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 1101
private static final int COUNT_BITS = Integer.SIZE - 3;
// 0000 0000 0000 0000 0000 0000 0000 0000 0001 1111 1111 1111 1111 1111 1111 1111
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
// 1111 1111 1111 1111 1111 1111 1111 1111 1110 0000 0000 0000 0000 0000 0000 0000
private static final int RUNNING    = -1 << COUNT_BITS;
// 0
private static final int SHUTDOWN   =  0 << COUNT_BITS;
// 0000 0000 0000 0000 0000 0000 0000 0000 0010 0000 0000 0000 0000 0000 0000 0000
private static final int STOP       =  1 << COUNT_BITS;
// 0000 0000 0000 0000 0000 0000 0000 0000 0100 0000 0000 0000 0000 0000 0000 0000
private static final int TIDYING    =  2 << COUNT_BITS;
// 0000 0000 0000 0000 0000 0000 0000 0000 0110 0000 0000 0000 0000 0000 0000 0000
private static final int TERMINATED =  3 << COUNT_BITS;

狀態之間的轉換如下:

  • RUNNING -> SHUTDOWN
    調用shutdown()后,可能會隱式地調用finalize()
  • (RUNNING or SHUTDOWN) -> STOP
    調用shutdownNow()后;
  • SHUTDOWN -> TIDYING
    當等待隊列和線程池是空的;
  • STOP -> TIDYING
    當線程池是空的;
  • TIDYING -> TERMINATED
    terminate()方法運行完成;

根據文檔上的闡述,一個Integer要表示兩個含義,這里采用的方法就是在32位長的二進制中用低29位來表達workerCount,剩下的高3位來代表runState狀態;

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

通過ctlOf來拼接runStateworkerCount,比如狀態為RUNNING,個數為0個,通過ctlOf(RUNNING, 1)計算出來的數值就是

1110 0000 0000 0000 0000 0000 0000 0000
|
0000 0000 0000 0000 0000 0000 0000 0001
=
1110 0000 0000 0000 0000 0000 0000 0001

那么runState狀態的計算方法就是

1110 0000 0000 0000 0000 0000 0000 0001
&
~(0001 1111 1111 1111 1111 1111 1111 1111)
->
1110 0000 0000 0000 0000 0000 0000 0000
=
1110 0000 0000 0000 0000 0000 0000 0001

只取高3位,而workerCount就是取低29位;

前置知識

要掌握整個流程,先需要了解一些源碼之中出現的屬性或類;

// 保持活動線程的最小數量,最小值為0,allowCoreThreadTimeOut設置后corePoolSize就無效了;
private volatile int corePoolSize;
// 線程池容量的最大值
private volatile int maximumPoolSize;

corePoolSizemaximumPoolSize的說明參數上面;

線程工廠
private volatile ThreadFactory threadFactory;

所有線程都是通過該工廠創建的,默認使用的是DefaultThreadFactory,創建線程時可能會遇到創建失敗的情況,比如OutOfMemoryError,所以說調用者需要處理啟動線程失敗的現象,比如進行清理并關閉線程池,讓其正確的退出;

Worker
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
    /** 每個worker都對應一個線程,如果為null則表示創建線程失敗,通常是異常如OOM */
    final Thread thread;
    /** Worker捆綁的FutureTask,可能是null */
    Runnable firstTask;
    /** 完成的任務的計數器 */
    volatile long completedTasks;

    Worker(Runnable firstTask) {
        setState(-1); // 禁止中斷,直到調用runWorker函數
        this.firstTask = firstTask;
        // 將自己(worker實現Runnable接口)傳遞給thread
        this.thread = getThreadFactory().newThread(this);
    }
    // Thread.run的委托
    public void run() { runWorker(this); }
    ... 
}

Worker主要是維護線程的中斷狀態,對AbstractQueuedSynchronizer類進行了擴展,實現了一個簡單的不可重入互斥鎖,以簡化獲取和釋放每個任務執行的鎖。不是使用ReentrantLock是因為不希望worker在調用諸如setCorePoolSize之類的池控制方法時能夠重新獲得鎖。同時為了在線程真正開始運行任務之前抑制中斷事件,將鎖狀態初始化為負值直到在runWorker中清除;

這是文檔中對于Worker類的解釋,提到的以下關鍵的幾點

  1. 實現了一個簡單的不可重入互斥鎖,以簡化獲取和釋放每個任務執行的鎖;
protected boolean tryAcquire(int unused) {
    if (compareAndSetState(0, 1)) {
        setExclusiveOwnerThread(Thread.currentThread());
        return true;
    }
    // 加鎖失敗
    // 重入鎖的話這里會執行重入的邏輯,這里就簡單的返回fail
    return false; 
}

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); }

省去了ReentrantLock中復雜的重入、排隊等待邏輯,加鎖成功就成功,失敗就失敗;

  1. 為了在線程真正開始運行任務之前抑制中斷事件,將鎖狀態初始化為負值;
Worker(Runnable firstTask) {
    setState(-1); // 禁止中斷,直到調用runWorker函數
    this.firstTask = firstTask;
    this.thread = getThreadFactory().newThread(this);
}

在初始化Worker時將state設置為-1,表示不響應中斷事件,具體就是執行worker#tryLock失敗,在開啟任務runWorker后才允許線程響應中斷;

  1. 不是使用重入鎖是因為不希望worker在調用諸如setCorePoolSize之類的池控制方法時能夠重新獲得鎖;
    這個光看字面意思會較難以理解,具體就是interruptIdleWorkers處理中斷有關,后面看到再分析;
Workers
private final HashSet<Worker> workers = new HashSet<Worker>();

Worker是保存在一個HashSet集合中,遍歷、添加和刪除操作之前需要持有下面這個鎖才能操作;

private final ReentrantLock mainLock = new ReentrantLock();

看下文檔中對這個的解釋:

雖然我們可以使用某種類型的并發集,但事實證明使用鎖通常更好。其中一個原因是,它串行化了interruptIdleWorkers操作,從而避免了不必要的中斷風暴,尤其是在調用shutdown。否則,退出的線程將同時中斷那些尚未中斷的線程。它還簡化了一些相關的統計數據如最大池大小等。我們同時也在shutdownshutdownNow中持有鎖;

假如說worker集合使用的是并發安全的集合比如ConcurrentHashMap,后者在遍歷時做了很多功夫保證線程安全,但是依賴消耗額外的空間,在interruptIdleWorkers中需要對worker集合進行遍歷,使用重入鎖可以很簡單保證每個worker中線程中斷狀態的正確性;

private int largestPoolSize;

保存線程池的最大值,也是在持有mainLock下才能訪問;

private final BlockingQueue<Runnable> workQueue;

workQueue用來儲存排隊的任務,當當前運行的任務數量大于corePoolSize時,會暫時保存到該等待隊列中,默認是無界隊列LinkedBlockingQueue

添加worker
/* 
core:如果為真則使用corePoolSize作為界,否則使用maximumPoolSize;

firstTask:新線程應該首先運行的任務(如果沒有則為null)。當隊列小于corePoolSize的線程
(在這種情況下會啟動一個線程)或隊列已滿時,使用一個初始的第一個任務創建Workers來繞過隊列。
最初,空閑線程通常是通過prestartCoreThread創建的,或者用來替換其他即將完成的worker。
*/
private boolean addWorker(Runnable firstTask, boolean core) {
    // firstTask不為null意味著worker
    retry:
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);
        // 僅在必要時檢查隊列是否為空
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
                // 意味著連接池調用了shutdown或者已經被關閉
                // && workQueue不為空
                // 或者firstTask為null
                // 或者連接池調用了shutdown,正處于SHUTDOWN狀態,此時不接受新任務
            return false;
        // 準備添加新任務
        for (;;) {
            int wc = workerCountOf(c);
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize)) // 根據core參數決定采用哪個值
                return false;
            // 更新workerCount
            if (compareAndIncrementWorkerCount(c))
                // cas成功,更新workerCount=workerCount+1
                break retry;
            // cas失敗,被其他線程搶占資源,重試
            c = ctl.get();  // Re-read ctl
            if (runStateOf(c) != rs)
                // runState狀態不再是RUNNING
                continue retry;
            // CAS因workerCount變更而失效;重試該循環
        }
    }
    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
        w = new Worker(firstTask); // 構建worker
        final Thread t = w.thread;
        if (t != null) {
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                // 持有鎖時重新檢查。
                // 退出當ThreadFactory故障,或者在獲取鎖之前關閉線程池。
                int rs = runStateOf(ctl.get());
                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) { // rs處于SHUTDOWN狀態
                    // runState是RUNNING或者runState是SHUTDOWN
                    // SHUTDOWN不允許接受新的任務,但是還會處理在等待隊列中的任務,所以firstTask需要為null
                    if (t.isAlive())
                        // 為什么要有這個判斷呢
                        // 如果對一個線程調用兩次start,那么第二次會拋出該異常
                        // 這里是防止workers集合添加worker后但是后面的t.start()卻啟動失敗
                        // 符合fast-fail思想
                        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;
}

addWorker主要是分為兩個階段,第一個階段是判斷連接池狀態是否處于運行RUNNING狀態,如果狀態處于SHUTDOWN,此時連接池是不接受新的任務,如果狀態處于可接受新任務的狀態且當前的worker數量小于預設條件,使用CAS更新workerCount;第二階段就是在成功更新workerCount后,將任務包裝成Worker類后添加到workers合集中;

也就是說這個方法的核心功能就是將FutureTask包裝成Worker并添加到workers合集中,并更新workerCount;該方法返回失敗的條件如下:

  • 線程池狀態不是RUNNING且,連接池調用了shutdown,正處于SHUTDOWN狀態,或者調用addWorker時傳的firstTask參數為null,或者任務等待隊列不是空的;
  • core為true條件下workerCount小于corePoolSize或者core為false條件下workerCount小于maximumPoolSize
  • 啟動線程時失敗
提交任務
public <T> Future<T> submit(Runnable task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task, null);
    execute(ftask);
    return ftask;
}

protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
    return new FutureTask<T>(callable);
}

默認使用的是FutureTask來包裝任務,看到protected關鍵字就意味著我們可以自己來擴展newTaskFor的返回結果,比如ForkJoinPool

execute
public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    int c = ctl.get();
    if (workerCountOf(c) < corePoolSize) {
        // 當前運行線程小于corePoolSize
        if (addWorker(command, true))
            // 添加worker成功并將thread啟動,直接返回
            return;
        // addWorker失敗,常見的情況就是調用shutdown或者
        // 因為其他線程添加成功導致現在wc大于corePoolSize
        c = ctl.get(); // 重新獲取runState和workerCount
    }
    // 條件:
    // 當前運行線程大于等于corePoolSize,或者addWorker失敗;

    // 如果線程池沒有被關閉,向等待隊列中添加任務
    if (isRunning(c) && workQueue.offer(command)) {
        // 進入排隊隊列成功
        int recheck = ctl.get(); // 重新獲取runState和workerCount
        if (! isRunning(recheck) && remove(command)) // 檢查線程是否還處于RUNNING狀態,不是的話進行回滾
            reject(command); // 拒絕任務
        else if (workerCountOf(recheck) == 0)
            // runState=RUNNING,workerCount=0,重新添加worker到workerSet
            addWorker(null, false);
    }
    // runState不再是RUNNING,或者向等待隊列中入隊失敗,即等待隊列以及滿了
    else if (!addWorker(command, false)) // 再次嘗試添加worker,但是由maximumPoolSize決定上限
        reject(command);
}
// 回滾任務
public boolean remove(Runnable task) {
    // 移除排隊隊列的任務
    boolean removed = workQueue.remove(task);
    tryTerminate(); // In case SHUTDOWN and now empty
    return removed;
}

提交任務時大概會經歷這幾個過程:

  1. 如果運行的線程小于corePoolSize,則嘗試啟動一個新線程執行任務。對addWorker失敗的線程重新檢查runState和workerCount;
  2. 如果一個任務可以成功地進入排隊隊列,如果發現當前的工作線程數量為0時會再次嘗試添加一個Worker線程,因為自上次檢查以來已有線程退出(之前發現workerCount大于corePoolSize,現在workerCount為0,意味著有Worker已經完成退出),注意這里addWorker(null, false),傳進去的任務是null,因為這時候任務剛剛進入排隊隊列里面;
    或者這時候線程池后調用了shutdown關閉,會將在等待隊列中的任務回滾;
  3. 如果排隊隊列已經滿了而導致不能入隊,則再次嘗試添加新線程。如果添加失敗,意味著線程池已經飽和,因此拒絕任務;

如果addWorker返回成功,那么這時候線程已經開始執行相應的任務了;

執行任務
final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask; // 可能為null
    w.firstTask = null;
    w.unlock(); // // 開始允許worker響應中斷
    boolean completedAbruptly = true;
    try {
        while (task != null || (task = getTask()) != null) {
            // 獲取到了任務
            w.lock(); // 加非重入鎖,保證每個線程同一時刻只能執行一個任務
            // 如果線程池處于STOP狀態通常就是調用了shutdownNow,確保所有worker被中斷;
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt();
            try {
                beforeExecute(wt, task); // hook
                Throwable thrown = null;
                try {
                    // 注意這個try-catch的對象是FutureTask#run
                    task.run(); // 最終執行任務的調用起點,調用的就是FutureTask#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); // hook
                }
            } finally {
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        processWorkerExit(w, completedAbruptly);
    }
}

firstTask也是有空的可能性,比如上面提到的剛開始添加addWorker時發現workerCount大于corePoolSize,再進入排隊隊列后發現當前workerCount為0,意味著一些Worker已經完成退出空出了空閑容量,那么會重新執行addWorker(null, false),也就是firstTask為null的原因之一;

整個方法的大概執行流程:

  1. firstTask不為空的情況下,那么就從該初始任務開始,否則需要通過getTask從等待隊列獲取,一般情況下只要線程池在運行中,就可以獲得任務,但是有可能會返回null,原因下面有總結;

  2. 如果獲取到了任務,需要確保線程池處于可響應任務的狀態(RUNNING或者SHUTDOWN),否則線程池被關閉了要確保中斷事件通知到每個Worker關聯的線程;

  3. 每個任務運行之前都有一個對beforeExecute鉤子的調用,但這也可能會拋出一個異常,在這種情況下我們在不處理任務直接退出worker

  4. 假設beforeExecute正常完成,就會開始執行這個任務,并儲存它可能拋出的任何異常并發送給afterExecute,注意這里的try-catch的對象是FutureTask#run,但是FutureTask#run內部也是有個try-catch,那個try-catch的對象才是我們執行的任務(就是下面這個匿名內部類);在afterExecute中看到的任何異常都會導致線程死亡和Worker退出;

ExecutorService executorService = new Executors.newFixedThreadPool(1);
try {
    Future future = executorService.submit(() -> {
        // 這個內部類的異常由FutureTask捕獲并儲存
        Object obj = null;
        System.out.println(obj.toString());
    });
    future.get(); // 獲取FutureTask的結果或者異常
} catch (Exception e) {
    e.printStackTrace();
}
  1. 如果獲取不到任務或者執行過程中遇到異常而導致的非正常退出,最后都是通過processWorkerExit關閉Worker
private Runnable getTask() {
    boolean timedOut = false;
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

        // 僅在必要時檢查隊列是否為空
        // ArrayBlockingQueue#isEmpty是要加鎖的
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            // runState處于SHUTDOWN狀態時不再接受新的任務,
            // runState處于STOP及以上時不會處理排隊的任務,同時等待隊列為空了也是如此(因為沒有排隊任務了)
            decrementWorkerCount(); // workerCount--
            return null; // 沒有任務可以處理了
        }
        int wc = workerCountOf(c);
        // 判斷worker允不允許超時,allowCoreThreadTimeOut為true或者是非核心的worker都會超時
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            // 滿足條件:
            // 1.當前的workerCount已經超過了maximumPoolSize所設置的最大容量,或者(worker允許超時 || 先前已經獲取任務失敗)
            // 2.等待隊列不為空,或者workerCount > 1
            if (compareAndDecrementWorkerCount(c)) // workerCount--
                return null;
            continue; // cas失敗,重新來一遍檢查
        }
        try {
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : // 可超時的阻塞等待
                workQueue.take(); // 阻塞直到有任務到來
            // 一般來說workerCount < corePoolSize 就會一直阻塞等待任務;
            // 如果設置了allowCoreThreadTimeOut 或者 corePoolSize < workerCount < maximumPoolSize
            // 就是進行可超時的等待任務,搭配keepAliveTime熟悉來處理
            if (r != null)
                return r;
            timedOut = true; // 沒有獲取到任務
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

getTask可能返回空的場景如下:

  1. 有超出maximumPoolSize大小的worker進來;
  2. 線程池已經被關閉了,即狀態不為RUNNINGSHUTDOWN
  3. 線程池處于SHUTDOWN狀態,即調用了shutdown(),且等待隊列為空;
  4. worker從等待隊列中獲取任務超時,即滿足allowCoreThreadTimeOut || workerCount > corePoolSize條件;
private void processWorkerExit(Worker w, boolean completedAbruptly) {
    // completedAbruptly為true意味著在worker執行過程中因為異常而導致非正常退出
    if (completedAbruptly) // 如果abrupt為true, 需要調整workerCount
        decrementWorkerCount(); // workerCount--
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        completedTaskCount += w.completedTasks;
        workers.remove(w);
    } finally {
        mainLock.unlock();
    }
    tryTerminate(); // 嘗試進入TERMINATE狀態
    int c = ctl.get();
    if (runStateLessThan(c, STOP)) { // runState處于RUNNING或者SHUTDOWN狀態
        if (!completedAbruptly) {
            // worker正常退出
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            if (min == 0 && ! workQueue.isEmpty())
                // 等待隊列還有任務的情況下,至少要保持一個常駐worker存在
                min = 1;
            if (workerCountOf(c) >= min)
                // 如果當前worker數量過多,則不需要創建一個新的worker
                return;
        }
        // worker非正常退出,需要重新創建一個
        addWorker(null, false);
    }
}

無論是正常還是非正常退出,每個Worker退出后都會執行這個方法,首先是將其從worker集合中移除,然后判斷是否能進入TERMINATE狀態,最后通過corePoolSize來判斷是否需要保持最低限度的worker存活來創建worker

final void tryTerminate() {
    for (;;) {
        int c = ctl.get(); // 獲取workerCount和runState
        if (isRunning(c) ||
            runStateAtLeast(c, TIDYING) ||
            (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
            // 還不能轉換到TERMINATEDe狀態的情況:
            // 1. 線程池狀態為RUNNING
            // 2. 線程池狀態為TIDYING或者已經是TERMINATED
            // 3. 線程池處于SHUTDOWN狀態,但是等待隊列中還有任務需要處理
            return;
        // 到這里意味著滿足切換到終止狀態的條件了
        if (workerCountOf(c) != 0) {
            // 如果workerCount非零,則中斷空閑的worker以確保關閉信號傳播
            interruptIdleWorkers(ONLY_ONE);
            // 每個worker退出時都會重新走到tryTerminate方法
            return;
        }
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                // 切換到TIDYING狀態成功
                try {
                    terminated(); // hook
                } finally {
                    ctl.set(ctlOf(TERMINATED, 0));
                     // 切換到TERMINATED狀態
                     termination.signalAll(); // 通知所有等待termination事件的線程
                }
                return; // 線程池終止成功
            }
        } finally {
            mainLock.unlock();
        }
        // CAS失敗,重試
    }
 }

在一些地方都會有這個判斷方法tryTerminate,該方法主要是嘗試將符合條件的線程池的狀態變成TERMINATED,當然不符合狀態就不會發生什么事情;

小結

到這里線程池基本的運行流程已經清晰了,重新梳理一下;
首先是corePoolSizemaximumPoolSize的關系,

無界隊列

如果我們在構造器中使用的是無界隊列(或者說容量很大的有界隊列),每一個submit的任務會被封裝在一個FutureTask里面,然后判斷當前任務數是否少于corePoolSize,如果是則創建Worker開始執行;如果不是會加入等待隊列中;

無界隊列的特點就是只創建和corePoolSize數目相同的Worker執行,其他多余的任務將排隊等待,這種模式的適合于CPU密集型任務,將corePoolSize設置為CPU數目一樣或者幾倍,可以減少線程切換時帶來的上下文切換等消耗,但是缺點就是提交任務的速度大于執行任務的速度時會造成任務累積,同時大量占用內存;

有界隊列

如果我們在構造器中使用的是有界隊列(容量不大),分為幾個步驟:

  1. 同樣每一個submit的任務會被封裝在一個FutureTask里面,然后判斷當前任務數是否少于corePoolSize,如果是則創建Worker開始執行;

  2. 如果當前任務數大于corePoolSize,就會嘗試加入等待隊列,如果添加失敗,則會根據maximumPoolSize決定是否要拒絕任務添加,如果小于當前運行的worker小于maximumPoolSize,會創建一個新的worker執行任務,如果大于maximumPoolSize就會拒絕該任務;

這種模式的特點就是worker的數量會動態增減,最低會保持corePoolSize數量個worker存活,最高允許創建maximumPoolSize個任務(corePoolSizemaximumPoolSize當然也可以一樣);適用的場景比如IO密集型場景,可能大量的時間都被阻塞在等待IO,這時候大容量的線程池能允許處理更多的任務,更加充分的利用CPU;

關閉線程池
public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess(); // 檢查權限是否允許執行shutdown
        advanceRunState(SHUTDOWN); // 將狀態修改成SHUTDOWN
        interruptIdleWorkers(); // 中斷所有可中斷的worker
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
        mainLock.unlock();
    }
    tryTerminate(); // 將狀態變成TERMINATED
}

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()) {
                // worker.tryLock成功意味著Worker處于可中斷狀態,
                // 即調用了Worker#runWorker
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                } finally {
                    w.unlock();
                }
            }
            if (onlyOne)
                break;
        }
    } finally {
        mainLock.unlock();
    }
}

調用shutdown關閉線程池,先是將狀態變成SHUTDOWN,然后中斷所有worker,這里的interruptIdleWorkers中斷的可中斷的Worker,即調用了runWorker且不是在執行任務過程的worker,因為worker執行任務前會加鎖;

public List<Runnable> shutdownNow() {
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess(); // 檢查權限是否允許執行shutdown
        advanceRunState(STOP); // 將狀態修改成STOP
        interruptWorkers(); // 嘗試中斷所有允許中斷的worker
        tasks = drainQueue(); // 取出所有等待的任務
    } finally {
        mainLock.unlock();
    }
    tryTerminate(); // 將線程池狀態修改成Terminate
    return tasks;
}
// 中斷所有活躍的workers
private void interruptWorkers() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        for (Worker w : workers)
            w.interruptIfStarted();
    } finally {
        mainLock.unlock();
    }
}

void interruptIfStarted() {
    Thread t;
    if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
        try {
            t.interrupt(); // 中斷任務,但是如果不響應中斷異常的線程可能不會發生什么,所以并不一定保證中斷成功
        } catch (SecurityException ignore) {
        }
    }
}

private List<Runnable> drainQueue() {
    BlockingQueue<Runnable> q = workQueue;
    ArrayList<Runnable> taskList = new ArrayList<Runnable>();
    q.drainTo(taskList); // 一次性取出所有任務
    if (!q.isEmpty()) { // 如果隊列是DelayQueue,可能drainTo沒法取出所有元素
        for (Runnable r : q.toArray(new Runnable[0])) { // 輪詢遍歷
            if (q.remove(r))
                taskList.add(r);
        }
    }
    return taskList;
}

shutdownNow會嘗試停止所有正在執行的任務,并停止等待任務的處理,同時返回正在等待執行的任務列表。interruptWorkers中會嘗試中斷所有活躍的worker,但是也只是嘗試,如果任務不能響應中斷任務,都可能永遠不會終止該線程;

參數調整

內置的一些方法允許動態調整線程池的核心參數;

public void setCorePoolSize(int corePoolSize) {
    if (corePoolSize < 0)
        throw new IllegalArgumentException();
    int delta = corePoolSize - this.corePoolSize; // delta>0,增加核心核數,delta<0,減少核心核數
    this.corePoolSize = corePoolSize;
    if (workerCountOf(ctl.get()) > corePoolSize)
        // 如果當前的工作線程數量大于新的corePoolSize
        interruptIdleWorkers(); // 中斷空閑的worker
    else if (delta > 0) {
        int k = Math.min(delta, workQueue.size());
        // 由于不確定是否需要那么多線程
        // 所以啟動和當前等待隊列任務一樣大小的worker,如果隊列在此過程中變為空,則停止。
        while (k-- > 0 && addWorker(null, true)) {
            if (workQueue.isEmpty())
                break;
        }
    }
}

指定一個corePoolSize,如果是將該值調小,則會即刻嘗試中斷空閑中的線程;如果是將該值調大,會從新corePoolSize和等待隊列的大小中選一個最小值,然后創建與最小值一樣的worker,注意這里的調整不是一觸而就的,而是慢慢的將以前的worker退出或者慢慢的增加worker,直到滿足新corePoolSize值;

public void setMaximumPoolSize(int maximumPoolSize) {
    if (maximumPoolSize <= 0 || maximumPoolSize < corePoolSize)
        throw new IllegalArgumentException();
    this.maximumPoolSize = maximumPoolSize;
    if (workerCountOf(ctl.get()) > maximumPoolSize)
        interruptIdleWorkers();
}

同理設置maximumPoolSize時,當前worker數量大于新值時,同樣會嘗試中斷空閑線程,讓worker數量慢慢將到新的maximumPoolSize

public void setKeepAliveTime(long time, TimeUnit unit) {
    if (time < 0)
        throw new IllegalArgumentException();
    if (time == 0 && allowsCoreThreadTimeOut())
        throw new IllegalArgumentException("Core threads must have nonzero keep alive times");
    long keepAliveTime = unit.toNanos(time);
    long delta = keepAliveTime - this.keepAliveTime;
    this.keepAliveTime = keepAliveTime;
    if (delta < 0)
        interruptIdleWorkers();
}

設置線程在終止之前保持空閑狀態的時間限制,如果當前池中線程的數量超過了核心數量,那么多余的線程將被終止。

public void purge() {
    final BlockingQueue<Runnable> q = workQueue;
    try {
        Iterator<Runnable> it = q.iterator();
        while (it.hasNext()) {
            Runnable r = it.next();
            if (r instanceof Future<?> && ((Future<?>)r).isCancelled())
                it.remove();
        }
    } catch (ConcurrentModificationException fallThrough) {
            // 如果在遍歷過程中遇到干擾,選擇慢速路徑。
            // 為遍歷創建副本,并為已取消的項調用remove。
            // 慢路徑更可能是O(N*N)
        for (Object r : q.toArray())
            if (r instanceof Future<?> && ((Future<?>)r).isCancelled())
                q.remove(r);
    }
    tryTerminate();
}

嘗試從工作隊列中刪除所有已被取消的任務,這種方法可以用作存儲回收操作;被取消的任務永遠不會執行,但可能會累積在工作隊列中,直到工作線程可以主動刪除它們。現在可以調用這個purge方法嘗試刪除它們。

public boolean prestartCoreThread() {
    return workerCountOf(ctl.get()) < corePoolSize &&
        addWorker(null, true);
}

如果當前的核心線程沒有達到設置的corePoolSize,則啟動一個worker,這是一種預加載機制,在某些場景下可能有用,比如剛剛把corePoolSize的數量從1調整到n>1,在任務來臨前可以快速啟動一個常駐worker;

 public int prestartAllCoreThreads() {
     int n = 0;
     while (addWorker(null, true))
         ++n;
     return n;
 }

當然還有一次性啟動corePoolSize個worker的方法可供選擇;

總結

日常開發中無時無刻不在接觸線程池或連接池,通過學習ThreadPoolExecutor,幫助我們更好的理解內部的機理,從而衍生到比如阿帕奇的httpcomponents中PoolingHttpClientConnectionManager,或者NGINX的連接池,或者Rabbitmq的連接池,看看是不是有異曲同工之妙,總之看一個積累一個,日積月累總會有所突破;

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。