【Java多線程-2】Java線程池詳解

通過前文 線程的創建與使用 ,我們對線程有了一定了解。線程的創建與銷燬需要依賴操作系統,其代價是比較高昂的,頻繁地創建與銷燬線程對系統性能影響較大。
出於線程管理的需要,線程池應運而生。線程池是一種多線程處理形式,處理過程中將任務提交到線程池,任務的執行交由線程池來管理。使用線程池的好處在於:

  • 降低資源消耗:線程池通常會維護一些線程(數量爲 corePoolSize),這些線程被重複使用來執行不同的任務,任務完成後不會銷燬。在待處理任務量很大的時候,通過對線程資源的複用,避免了線程的頻繁創建與銷燬,從而降低了系統資源消耗。
  • 提高響應速度:由於線程池維護了一批 alive 狀態的線程,當任務到達時,不需要再創建線程,而是直接由這些線程去執行任務,從而減少了任務的等待時間。
  • 提高線程的可管理性:使用線程池可以對線程進行統一的分配,調優和監控。

1 Executor框架

在Java中,線程池是由Executor框架實現的,Executor是最頂層的接口定義,其子類和實現類包括:ExecutorService,ScheduledExecutorService,ThreadPoolExecutor,ScheduledThreadPoolExecutor,ForkJoinPool等。
類圖如下:
圖片來自網絡

  1. Executor:Executor是一個接口,只定義了一個execute()方法(void execute(Runnable command);),只能提交Runnable形式的任務,不支持提交Callable帶有返回值的任務。
  2. ExecutorService:ExecutorService在Executor的基礎上加入了線程池的生命週期管理,可以通過shutdown或者shutdownNow方法來關閉線程池,關於這兩個方法後文有詳細說明。ExecutorService支持提交Callable形式的任務,提交完Callable任務後拿到一個Future(代表一個異步任務執行的結果)。
  3. ThreadPoolExecutor:是線程池中最核心的類,後面有詳細說明。
  4. ScheduledThreadPoolExecutor:ThreadPoolExecutor子類,它在ThreadPoolExecutor基礎上加入了任務定時執行的功能。

2 線程池的創建

Executors中提供了一系列靜態方法創建線程池:

  • newSingleThreadExecutor:一個單線程的線程池。如果因異常結束,會再創建一個新的,保證按照提交順序執行。
  • newFixedThreadPool:創建固定大小的線程池。根據提交的任務逐個增加線程,直到最大值保持不變。如果因異常結束,會新創建一個線程補充。
    newCachedThreadPool:創建一個可緩存的線程池。會根據任務自動新增或回收線程。
  • newScheduledThreadPool:支持定時以及週期性執行任務的需求。
  • newWorkStealingPool:JDK8新增,根據所需的並行層次來動態創建和關閉線程,通過使用多個隊列減少競爭,底層使用ForkJoinPool來實現。優勢在於可以充分利用多CPU,把一個任務拆分成多個“小任務”,放到多個處理器核心上並行執行;當多個“小任務”執行完成之後,再將這些執行結果合併起來即可。

觀察源碼發現,這些靜態方法其實還是調用了 ThreadPoolExecutor 這個類,下面是一部分源碼:

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

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

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
}

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
}

3 線程池的實現

前面提到的java.uitl.concurrent.ThreadPoolExecutor類是線程池中最核心的一個類,線程池的諸多功能都是在這個類中實現的,值得我們好好研究一番。

一開始,我們先用一個通俗的例子來幫助我們理解線程池的運行機制:
假如我們建了一家加工廠,那麼第一個問題來了:工人編制規模是多少?(這個數字就對應線程池的 corePoolSize,即線程池核心線程數量)

接下來,假定工廠滿編20個工人,那麼第二個問題就是工人怎麼招?(也就是線程池的線程初始化策略)根據老闆的豪氣程度無非有三種方式:

  • 第一種,老闆摳到極致,不見兔子不撒鷹,接一部分活兒招一個工人,直到滿編(也就是不進行線程池的線程初始化)
  • 第二種,老闆精打細算,先招一個人充充門面(即調用 prestartCoreThread() 初始化一個核心線程)
  • 第三種,老闆豪氣萬丈,不管有沒有活幹,先把工人都安排到位(即調用prestartAllCoreThreads(),初始化所有核心線程)

不管怎麼樣,工人總是要來的,那麼工人的勞動合同該如何籤呢?是終身勞動合同,廠子不倒,人員不散?還是鐵打的營盤流水的兵?

  • 前者就是 allowCoreThreadTimeOut 設置爲 false,即核心線程不設置存活時間
  • 後者就是 allowCoreThreadTimeOut 設置爲 true,即核心線程設置存活時間,存活時間的長度就是 keepAliveTime

接下來,工廠的生產線也建設起來了(在線程池裏稱之爲 HashSet<Worker> workers),工人進入這條生產線進行生產。

完事俱備,工廠開始接受訂單。爲了更好地調度生產,一個調度員入職,英文名字execute。在拿到訂單後,調度員execute按既定流程開始工作:

  1. 清點一下當前的工人人數(即線程池的 poolSize),發現人員沒滿編,於是立馬招一個工人來接下這個工作任務。
  2. 工人人數滿編了,於是調度室把待加工的構建放置到工廠倉庫(即任務隊列BlockingQueue<Runnable> workQueue),等待有幹完活兒的工人來處理,當然工人是沒這個主動性的,所以又一個調度員 getTask 入職了,他的任務就是實時將倉庫的待加工任務分配給空閒下來的工人。
  3. 繁忙的時候,調度員execute發現工人滿負荷工作,倉庫也堆滿了,而訂單還在雪花般飛來,爲了把這些訂單消化掉,execute 趕緊招了一批臨時工,把工廠工人規模臨時擴大到極致(即 maximumPoolSize,線程池最大容量)。當然當生產任務沒那麼繁忙時,這些臨時工就要被裁撤了,畢竟臨時工是有成本的。
  4. 當臨時工都到位後,訂單仍然源源不斷,老闆也只能忍痛割愛,拒絕後續訂單了(即線程池的拒絕策略)。

工廠當然不能稀裏糊塗地一門心思生產,畢竟工廠業績老闆是很關心的,於是生產總量要被統計(即 completedTaskCount,線程池已完成的任務數)。工廠最多有過多少工人也被順手統計了(即 largestPoolSize,線程池出現過的最大線程數)

當有一天,工廠因爲某原因關閉時,會有兩種情形:

  • 工廠宣佈關閉,不再接受訂單,但會把已經接受的訂單做完,然後遣散工人(即 調用 shutdown()關閉線程池,比較柔和的關閉方式)
  • 工廠宣佈立即關閉,不僅不再接受訂單,而且把倉庫裏的待加工組件清空,工人停止手頭的工作並遣散(即調用 shutdownNow(),比較激進的關閉方式)。

根據上面的例子,提煉出線程池執行任務的流程圖,當然這個流程圖比較簡略。
在這裏插入圖片描述

3.1 構造方法

在ThreadPoolExecutor類中提供了四個構造方法:

public class ThreadPoolExecutor extends AbstractExecutorService {
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue);
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
}

下面解釋下一下構造器中各個參數的含義:

  • corePoolSize:線程池容量,這個參數跟後面講述的線程池的實現原理有非常大的關係。在創建了線程池後,默認情況下,線程池中並沒有任何線程,而是等待有任務到來才創建線程去執行任務,除非調用了prestartAllCoreThreads()或者prestartCoreThread()方法(從這2個方法的名字就可以看出,是預創建線程的意思,即在沒有任務到來之前就創建corePoolSize個線程或者一個線程)。當線程池中的線程數目達到corePoolSize後,就會把到達的任務放到緩存隊列當中。

  • maximumPoolSize:線程池最大線程數,這個參數也是一個非常重要的參數,它表示在線程池中最多能創建多少個線程。

  • keepAliveTime:表示線程沒有任務執行時最多保持多久時間會終止。默認情況下,只有當線程池中的線程數大於corePoolSize時,keepAliveTime纔會起作用,直到線程池中的線程數不大於corePoolSize,即當線程池中的線程數大於corePoolSize時,如果一個線程空閒的時間達到keepAliveTime,則會終止,直到線程池中的線程數不超過corePoolSize。但是如果調用了allowCoreThreadTimeOut(boolean)方法,在線程池中的線程數不大於corePoolSize時,keepAliveTime參數也會起作用,直到線程池中的線程數爲0。

  • unit:參數keepAliveTime的時間單位,有7種取值,在TimeUnit類中有7種靜態屬性:

    1. 天:TimeUnit.DAYS;
    2. 小時:TimeUnit.HOURS;
    3. 分鐘:TimeUnit.MINUTES;
    4. 秒:TimeUnit.SECONDS;
    5. 毫秒:TimeUnit.MILLISECONDS;
    6. 微秒:TimeUnit.MICROSECONDS;
    7. 納秒:TimeUnit.NANOSECONDS;
  • workQueue:一個阻塞隊列,用來存儲等待執行的任務,這個參數的選擇也很重要,會對線程池的運行過程產生重大影響,一般來說,這裏的阻塞隊列有以下幾種選擇:

    1. ArrayBlockingQueue:基於數組結構的有界阻塞隊列,按FIFO排序任務;
    2. LinkedBlockingQuene:基於鏈表結構的阻塞隊列,按FIFO排序任務,吞吐量通常要高於ArrayBlockingQuene;
    3. SynchronousQuene:一個不存儲元素的阻塞隊列,每個插入操作必須等到另一個線程調用移除操作,否則插入操作一直處於阻塞狀態,吞吐量通常要高於LinkedBlockingQuene;
    4. priorityBlockingQuene:具有優先級的無界阻塞隊列;
  • threadFactory:線程工廠,主要用來創建線程;

  • handler:表示當拒絕處理任務時的策略,有以下四種取值:

    1. ThreadPoolExecutor.AbortPolicy:丟棄任務並拋出RejectedExecutionException異常。
    2. ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,但是不拋出異常。
    3. ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,然後重新嘗試執行任務(重複此過程)
    4. ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務

ThreadPoolExecutor、AbstractExecutorService、ExecutorService和Executor幾個之間的關係:

  • ThreadPoolExecutor繼承了AbstractExecutorService,AbstractExecutorService是一個抽象類,它實現了ExecutorService接口。
  • ExecutorService又是繼承了Executor接口,Executor是頂層接口。

3.2 重要成員變量

接下來看一下ThreadPoolExecutor類中其他的一些比較重要成員變量:

// 任務緩存隊列,用來存放等待執行的任務
private final BlockingQueue<Runnable> workQueue;
//線程池的主要狀態鎖,對線程池狀態(比如線程池大小、runState等)的改變都要使用這個鎖
private final ReentrantLock mainLock = new ReentrantLock(); 
//用來存放工作集
private final HashSet<Worker> workers = new HashSet<Worker>();  
//線程存活時間
private volatile long  keepAliveTime; 
//是否允許爲核心線程設置存活時間
private volatile boolean allowCoreThreadTimeOut; 
//核心池的大小(即線程池中的線程數目大於這個參數時,提交的任務會被放進任務緩存隊列)
private volatile int   corePoolSize; 
//線程池最大能容忍的線程數
private volatile int   maximumPoolSize; 
//線程池中當前的線程數  
private volatile int   poolSize;
//任務拒絕策略      
private volatile RejectedExecutionHandler handler; 
//線程工廠,用來創建線程
private volatile ThreadFactory threadFactory;  
//用來記錄線程池中曾經出現過的最大線程數 
private int largestPoolSize; 
//用來記錄已經執行完畢的任務個數  
private long completedTaskCount;   

3.3 線程池狀態

在ThreadPoolExecutor中定義了一個volatile變量,另外定義了幾個static final變量表示線程池的各個狀態:

volatile int runState;
static final int RUNNING    = 0;
static final int SHUTDOWN   = 1;
static final int STOP       = 2;
static final int TERMINATED = 3;

runState表示當前線程池的狀態,它是一個 volatile 變量用來保證線程之間的可見性。
下面的幾個static final變量表示runState可能的幾個取值,有以下幾個狀態:

  1. RUNNING:當創建線程池後,初始時,線程池處於RUNNING狀態;
  2. SHUTDOWN:如果調用了shutdown()方法,則線程池處於SHUTDOWN狀態,此時線程池不能夠接受新的任務,它會等待所有任務執行完畢;
  3. STOP:如果調用了shutdownNow()方法,則線程池處於STOP狀態,此時線程池不能接受新的任務,並且會去嘗試終止正在執行的任務;
  4. TERMINATED:當線程池處於SHUTDOWN或STOP狀態,並且所有工作線程已經銷燬,任務緩存隊列已經清空或執行結束後,線程池被設置爲TERMINATED狀態。

3.4 任務的執行

諸多函數的調用關係圖:
在這裏插入圖片描述

3.4.1 execute()

  • 方法說明:在ThreadPoolExecutor類中,任務提交方法的入口是execute(Runnable command) 方法(submit()方法也是調用了execute()),該方法其實只在嘗試做一件事:經過各種校驗之後,調用 addWorker(Runnable command,boolean core) 方法爲線程池創建一個線程並執行任務,與之相對應,execute() 的結果有兩個:

    1. 成功調用了addWorker()(剩下的執行任務要交給後續方法去完成了)
    2. 未能調用addWorker並拒絕本次任務,返回null。
  • 參數說明:

    1. Runnable command:待執行的任務
  • 執行流程:

    1. 通過 ctl.get() 得到線程池的當前線程數,如果線程數小於corePoolSize,則調用 addWorker(commond,true) 方法創建新的線程執行任務,否則執行步驟2;
    2. 步驟1失敗,說明已經無法再創建新線程,那麼考慮將任務放入阻塞隊列,等待執行完任務的線程來處理。基於此,判斷線程池是否處於Running狀態(只有Running狀態的線程池可以接受新任務),如果任務添加到任務隊列成功則進入步驟3,失敗則進入步驟4;
    3. 來到這一步需要說明任務已經加入任務隊列,這時要二次校驗線程池的狀態,會有以下情形:
      • 線程池不再是Running狀態了,需要將任務從任務隊列中移除,如果移除成功則拒絕本次任務
      • 線程池是Running狀態,則判斷線程池工作線程是否爲0,是則調用 addWorker(commond,true) 添加一個沒有初始任務的線程(這個線程將去獲取已經加入任務隊列的本次任務並執行),否則進入步驟4;
      • 線程池不是Running狀態,但從任務隊列移除任務失敗(可能已被某線程獲取?),進入步驟4;
    4. 將線程池擴容至maximumPoolSize並調用 addWorker(commond,false) 方法創建新的線程執行任務,失敗則拒絕本次任務
  • 流程圖:
    在這裏插入圖片描述

  • 源碼詳讀:

/**
 * Executes the given task sometime in the future.  The task
 * may execute in a new thread or in an existing pooled thread.
 * 在將來的某個時候執行給定的任務。任務可以在新線程中執行,也可以在現有的池線程中執行。
 * If the task cannot be submitted for execution, either because this
 * executor has been shutdown or because its capacity has been reached,
 * the task is handled by the current {@code RejectedExecutionHandler}.
 * 如果由於此執行器已關閉或已達到其容量而無法提交任務以供執行,則由當前的{@code RejectedExecutionHandler}處理該任務。
 * @param command the task to execute  待執行的任務命令
 * @throws RejectedExecutionException at discretion of
 *         {@code RejectedExecutionHandler}, if the task
 *         cannot be accepted for execution
 * @throws NullPointerException if {@code command} is null
 */
public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    /*
     * Proceed in 3 steps:
     *
     * 1. If fewer than corePoolSize threads are running, try to
     * start a new thread with the given command as its first
     * task.  The call to addWorker atomically checks runState and
     * workerCount, and so prevents false alarms that would add
     * threads when it shouldn't, by returning false.
     * 如果運行的線程少於corePoolSize,請嘗試以給定的命令作爲第一個任務啓動新線程。
     * 對addWorker的調用以原子方式檢查runState和workerCount,
     * 因此可以通過返回false來防止在不應該添加線程時出現錯誤警報。
     *
     * 2. If a task can be successfully queued, then we still need
     * to double-check whether we should have added a thread
     * (because existing ones died since last checking) or that
     * the pool shut down since entry into this method. So we
     * recheck state and if necessary roll back the enqueuing if
     * stopped, or start a new thread if there are none.
     * 如果一個任務可以成功排隊,那麼我們仍然需要仔細檢查兩點,其一,我們是否應該添加一個線程
     * (因爲自從上次檢查至今,一些存在的線程已經死亡),其二,線程池狀態此時已改變成非運行態。因此,我們重新檢查狀態,如果檢查不通過,則移除已經入列的任務,如果檢查通過且線程池線程數爲0,則啓動新線程。
     * 3. If we cannot queue task, then we try to add a new
     * thread.  If it fails, we know we are shut down or saturated
     * and so reject the task.
     * 3. 如果無法將任務加入任務隊列,則將線程池擴容到極限容量並嘗試創建一個新線程,
     * 如果失敗則拒絕任務。
     */
    int c = ctl.get();
   
    // 步驟1:判斷線程池當前線程數是否小於線程池大小
    if (workerCountOf(c) < corePoolSize) {
        // 增加一個工作線程並添加任務,成功則返回,否則進行步驟2
        // true代表使用coreSize作爲邊界約束,否則使用maximumPoolSize
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    // 步驟2:不滿足workerCountOf(c) < corePoolSize或addWorker失敗,進入步驟2
    // 校驗線程池是否是Running狀態且任務是否成功放入workQueue(阻塞隊列)
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        // 再次校驗,如果線程池非Running且從任務隊列中移除任務成功,則拒絕該任務
        if (! isRunning(recheck) && remove(command))
            reject(command);
        // 如果線程池工作線程數量爲0,則新建一個空任務的線程
        else if (workerCountOf(recheck) == 0)
            // 如果線程池不是Running狀態,是加入不進去的
            addWorker(null, false);
    }
    // 步驟3:如果線程池不是Running狀態或任務入列失敗,嘗試擴容maxPoolSize後再次addWorker,失敗則拒絕任務
    else if (!addWorker(command, false))
        reject(command);
}

3.4.2 addWorker()

  • 方法說明:
    addWorker(Runnable firstTask, boolean core) 方法,顧名思義,向線程池添加一個帶有任務的工作線程。

  • 參數說明:

    1. Runnable firstTask:新創建的線程應該首先運行的任務(如果沒有,則爲空)。
    2. boolean core:該參數決定了線程池容量的約束條件,即當前線程數量以何值爲極限值。參數爲 true 則使用corePollSize 作爲約束值,否則使用maximumPoolSize。
  • 執行流程:

    1. 外層循環判斷線程池的狀態是否可以新增工作線程。這層校驗基於下面兩個原則:
      • 線程池爲Running狀態時,既可以接受新任務也可以處理任務
      • 線程池爲關閉狀態時只能新增空任務的工作線程(worker)處理任務隊列(workQueue)中的任務不能接受新任務
    2. 內層循環向線程池添加工作線程並返回是否添加成功的結果。
      • 首先校驗線程數是否已經超限制,是則返回false,否則進入下一步
      • 通過CAS使工作線程數+1,成功則進入步驟3,失敗則再次校驗線程池是否是運行狀態,是則繼續內層循環,不是則返回外層循環
    3. 核心線程數量+1成功的後續操作:添加到工作線程集合,並啓動工作線程
      • 首先獲取鎖之後,再次校驗線程池狀態(具體校驗規則見代碼註解),通過則進入下一步,未通過則添加線程失敗
      • 線程池狀態校驗通過後,再檢查線程是否已經啓動,是則拋出異常,否則嘗試將線程加入線程池
      • 檢查線程是否啓動成功,成功則返回true,失敗則進入 addWorkerFailed 方法
  • 流程圖:
    在這裏插入圖片描述

  • 源碼詳讀:

private boolean addWorker(Runnable firstTask, boolean core) {
    // 外層循環:判斷線程池狀態
    retry:
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

        /** 
         * 1.線程池爲非Running狀態(Running狀態則既可以新增核心線程也可以接受任務)
         * 2.線程爲shutdown狀態且firstTask爲空且隊列不爲空
         * 3.滿足條件1且條件2不滿足,則返回false
         * 4.條件2解讀:線程池爲shutdown狀態時且任務隊列不爲空時,可以新增空任務的線程來處理隊列中的任務
         */
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;

		// 內層循環:線程池添加核心線程並返回是否添加成功的結果
        for (;;) {
            int wc = workerCountOf(c);
            // 校驗線程池已有線程數量是否超限:
            // 1.線程池最大上限CAPACITY 
            // 2.corePoolSize或maximumPoolSize(取決於入參core)
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize)) 
                return false;
            // 通過CAS操作使工作線程數+1,跳出外層循環
            if (compareAndIncrementWorkerCount(c)) 
                break retry;
            // 線程+1失敗,重讀ctl
            c = ctl.get();   // Re-read ctl
            // 如果此時線程池狀態不再是running,則重新進行外層循環
            if (runStateOf(c) != rs)
                continue retry;
            // 其他 CAS 失敗是因爲工作線程數量改變了,繼續內層循環嘗試CAS對線程數+1
            // else CAS failed due to workerCount change; retry inner loop
        }
    }

    /**
     * 核心線程數量+1成功的後續操作:添加到工作線程集合,並啓動工作線程
     */
    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
        final ReentrantLock mainLock = this.mainLock;
        w = new Worker(firstTask);
        final Thread t = w.thread;
        if (t != null) {
            // 下面代碼需要加鎖:線程池主鎖
            mainLock.lock(); 
            try {
                // Recheck while holding lock.  
                // Back out on ThreadFactory failure or if
                // shut down before lock acquired.
                // 持鎖期間重新檢查,線程工廠創建線程失敗或獲取鎖之前關閉的情況發生時,退出
                int c = ctl.get();
                int rs = runStateOf(c);

				// 再次檢驗線程池是否是running狀態或線程池shutdown但線程任務爲空
                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
                    // 線程已經啓動,則拋出非法線程狀態異常
                    // 爲什麼會存在這種狀態呢?未解決
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                    workers.add(w); //加入線程池
                    int s = workers.size();
                    // 如果當前工作線程數超過線程池曾經出現過的最大線程數,刷新後者值
                    if (s > largestPoolSize)
                        largestPoolSize = s; 
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();  // 釋放鎖
            }
            if (workerAdded) { // 工作線程添加成功,啓動該線程
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        //線程啓動失敗,則進入addWorkerFailed
        if (! workerStarted) 
            addWorkerFailed(w);
    }
    return workerStarted;
}

3.4.3 Worker類

Worker類是內部類,既實現了Runnable,又繼承了AbstractQueuedSynchronizer(以下簡稱AQS),所以其既是一個可執行的任務,又可以達到鎖的效果。
Worker類主要維護正在運行任務的線程的中斷控制狀態,以及其他次要的記錄。這個類適時地繼承了AbstractQueuedSynchronizer類,以簡化獲取和釋放鎖(該鎖作用於每個任務執行代碼)的過程。這樣可以防止去中斷正在運行中的任務,只會中斷在等待從任務隊列中獲取任務的線程。
我們實現了一個簡單的不可重入互斥鎖,而不是使用可重入鎖,因爲我們不希望工作任務在調用setCorePoolSize之類的池控制方法時能夠重新獲取鎖。另外,爲了在線程真正開始運行任務之前禁止中斷,我們將鎖狀態初始化爲負值,並在啓動時清除它(在runWorker中)。

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;
 
    /** Thread this worker is running in.  Null if factory fails. */
    final Thread thread; 
     
    /** Initial task to run.  Possibly null. */
    Runnable firstTask;
     
    /** Per-thread task counter */
    volatile long completedTasks;
 
    /**
     * Creates with given first task and thread from ThreadFactory.
     * @param firstTask the first task (null if none)
     */
    // 通過構造函數初始化,
    Worker(Runnable firstTask) {
        //設置AQS的同步狀態
        // state:鎖狀態,-1爲初始值,0爲unlock狀態,1爲lock狀態
        setState(-1); // inhibit interrupts until runWorker  在調用runWorker前,禁止中斷
       
        this.firstTask = firstTask;
        // 線程工廠創建一個線程
        this.thread = getThreadFactory().newThread(this); 
    }
 
    /** Delegates main run loop to outer runWorker  */
    public void run() {
        runWorker(this); //runWorker()是ThreadPoolExecutor的方法
    }
 
    // Lock methods
    // The value 0 represents the unlocked state. 0代表“沒被鎖定”狀態
    // The value 1 represents the locked state. 1代表“鎖定”狀態
 
    protected boolean isHeldExclusively() {
        return getState() != 0;
    }
 
    /**
     * 嘗試獲取鎖的方法
     * 重寫AQS的tryAcquire(),AQS本來就是讓子類來實現的
     */
    protected boolean tryAcquire(int unused) {
        // 判斷原值爲0,且重置爲1,所以state爲-1時,鎖無法獲取。
        // 每次都是0->1,保證了鎖的不可重入性
        if (compareAndSetState(0, 1)) {
            // 設置exclusiveOwnerThread=當前線程
            setExclusiveOwnerThread(Thread.currentThread()); 
            return true;
        }
        return false;
    }
 
    /**
     * 嘗試釋放鎖
     * 不是state-1,而是置爲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(); }
 
    /**
     * 中斷(如果運行)
     * shutdownNow時會循環對worker線程執行
     * 且不需要獲取worker鎖,即使在worker運行時也可以中斷
     */
    void interruptIfStarted() {
        Thread t;
        //如果state>=0、t!=null、且t沒有被中斷
        //new Worker()時state==-1,說明不能中斷
        if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
            try {
                t.interrupt();
            } catch (SecurityException ignore) {
            }
        }
    }
}

3.4.4 runWorker()

  • 方法說明:可以說,runWorker(Worker w) 是線程池中真正處理任務的方法,前面的execute() 和 addWorker() 都是在爲該方法做準備和鋪墊。

  • 參數說明:

    1. Worker w:封裝的Worker,攜帶了工作線程的諸多要素,包括 Runnable(待處理任務)、lock(鎖)、completedTasks(記錄線程池已完成任務數)
  • 執行流程:

    1. 判斷當前任務或者從任務隊列中獲取的任務是否不爲空,都爲空則進入步驟2,否則進入步驟3
    2. 任務爲空,則將completedAbruptly置爲false(即線程不是突然終止),並執行processWorkerExit(w,completedAbruptly) 方法進入線程退出程序
    3. 任務不爲空,則進入循環,並加鎖
    4. 判斷是否爲線程添加中斷標識,以下兩個條件滿足其一則添加中斷標識:
      1. 線程池狀態>=STOP,即STOP或TERMINATED
      2. 一開始判斷線程池狀態<STOP,接下來檢查發現Thread.interrupted()爲true,即線程已經被中斷,再次檢查線程池狀態是否>=STOP(以消除該瞬間shutdown方法生效,使線程池處於STOP或TERMINATED)
    5. 執行前置方法 beforeExecute(wt, task)(該方法爲空方法,由子類實現)後執行task.run() 方法執行任務(執行不成功拋出相應異常)
    6. 執行後置方法 afterExecute(task, thrown)(該方法爲空方法,由子類實現)後將線程池已完成的任務數+1,並釋放鎖。
    7. 再次進行循環條件判斷。
  • 流程圖:
    在這裏插入圖片描述

  • 源碼詳讀:

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    // allow interrupts
    // new Worker()是state==-1,此處是調用Worker類的tryRelease()方法,將state置爲0,而interruptIfStarted()中只有state>=0才允許調用中斷
    w.unlock(); 
            
    // 線程退出的原因,true是任務導致,false是線程正常退出
    boolean completedAbruptly = true; 
    try {
        // 當前任務和從任務隊列中獲取的任務都爲空,方停止循環
        while (task != null || (task = getTask()) != null) {
            //上鎖可以防止在shutdown()時終止正在運行的worker,而不是應對併發
            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
            /**
             * 判斷1:確保只有在線程處於stop狀態且wt未中斷時,wt纔會被設置中斷標識
             * 條件1:線程池狀態>=STOP,即STOP或TERMINATED
             * 條件2:一開始判斷線程池狀態<STOP,接下來檢查發現Thread.interrupted()爲true,即線程已經被中斷,再次檢查線程池狀態是否>=STOP(以消除該瞬間shutdown方法生效,使線程池處於STOP或TERMINATED),
             * 條件1與條件2任意滿意一個,且wt不是中斷狀態,則中斷wt,否則進入下一步
             */
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt(); //當前線程調用interrupt()中斷
             
            try {
                //執行前(空方法,由子類重寫實現)
                beforeExecute(wt, task);
                 
                Throwable thrown = null;
                try {
                    task.run();
                } 
                catch (RuntimeException x) {
                    thrown = x; throw x;
                } 
                catch (Error x) {
                    thrown = x; throw x;
                } 
                catch (Throwable x) {
                    thrown = x; throw new Error(x);
                } 
                finally {
                    //執行後(空方法,由子類重寫實現)
                    afterExecute(task, thrown); 
                }
            } 
            finally {
                task = null; 
                w.completedTasks++; //完成任務數+1
                w.unlock(); //釋放鎖
            }
        }
        // 
        completedAbruptly = false;
    } 
    finally {
        //處理worker的退出
        processWorkerExit(w, completedAbruptly);
    }
}

3.4.5 getTask()

  • 方法說明:由函數調用關係圖可知,在ThreadPoolExecutor類的實現中,Runnable getTask() 方法是爲 void runWorker(Worker w)方法服務的,它的作用就是在任務隊列(workQueue)中獲取 task(Runnable)。
  • 參數說明:無參數
  • 執行流程:
    1. 將timedOut(上次獲取任務是否超時)置爲false(首次執行方法,無上次,自然爲false),進入一個無限循環
    2. 如果線程池爲Shutdown狀態且任務隊列爲空(線程池shutdown狀態可以處理任務隊列中的任務,不再接受新任務,這個是重點)或者線程池爲STOP或TERMINATED狀態,則意味着線程池不必再獲取任務了,當前工作線程數量-1並返回null,否則進入步驟3
    3. 如果線程池數量超限制或者時間超限且(任務隊列爲空或當前線程數>1),則進入步驟4,否則進入步驟5。
    4. 移除工作線程,成功則返回null,不成功則進入下輪循環。
    5. 嘗試用poll() 或者 take()(具體用哪個取決於timed的值)獲取任務,如果任務不爲空,則返回該任務。如果爲空,則將timeOut 置爲 true進入下一輪循環。如果獲取任務過程發生異常,則將 timeOut置爲 false 後進入下一輪循環。
  • 流程圖:
    在這裏插入圖片描述
  • 源碼詳讀:
private Runnable getTask() {
    // 最新一次poll是否超時
    boolean timedOut = false; // Did the last poll() time out?

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

        // Check if queue empty only if necessary.
        /**
         * 條件1:線程池狀態SHUTDOWN、STOP、TERMINATED狀態
         * 條件2:線程池STOP、TERMINATED狀態或workQueue爲空
         * 條件1與條件2同時爲true,則workerCount-1,並且返回null
         * 注:條件2是考慮到SHUTDOWN狀態的線程池不會接受任務,但仍會處理任務
         */
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }

        int wc = workerCountOf(c);

        // Are workers subject to culling?
        /**
         * 下列兩個條件滿足任意一個,則給當前正在嘗試獲取任務的工作線程設置阻塞時間限制(超時會被銷燬?不太確定這點),否則線程可以一直保持活躍狀態
         * 1.allowCoreThreadTimeOut:當前線程是否以keepAliveTime爲超時時限等待任務
         * 2.當前線程數量已經超越了核心線程數
         */
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
            
        // 兩個條件全部爲true,則通過CAS使工作線程數-1,即剔除工作線程
        // 條件1:工作線程數大於maximumPoolSize,或(工作線程阻塞時間受限且上次在任務隊列拉取任務超時)
        // 條件2:wc > 1或任務隊列爲空
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            // 移除工作線程,成功則返回null,不成功則進入下輪循環
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }

	    // 執行到這裏,說明已經經過前面重重校驗,開始真正獲取task了
        try {
            // 如果工作線程阻塞時間受限,則使用poll(),否則使用take()
            // poll()設定阻塞時間,而take()無時間限制,直到拿到結果爲止
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            // r不爲空,則返回該Runnable
            if (r != null)
                return r;
            // 沒能獲取到Runable,則將最近獲取任務是否超時設置爲true
            timedOut = true;
        } catch (InterruptedException retry) {
            // 響應中斷,進入下一次循環前將最近獲取任務超時狀態置爲false
            timedOut = false;
        }
    }
}

3.4.6 processWorkerExit()

  • 方法說明:processWorkerExit(Worker w, boolean completedAbruptly),執行線程退出的方法

  • 參數說明:

    1. Worker w:要結束的工作線程。
    2. boolean completedAbruptly: 是否突然完成(異常導致),如果工作線程因爲用戶異常死亡,則completedAbruptly參數爲 true。
  • 執行流程:

    1. 如果 completedAbruptly 爲 true,即工作線程因爲異常突然死亡,則執行工作線程-1操作。
    2. 主線程獲取鎖後,線程池已經完成的任務數追加 w(當前工作線程) 完成的任務數,並從worker的set集合中移除當前worker。
    3. 根據線程池狀態進行判斷是否執行tryTerminate()結束線程池。
    4. 是否需要增加工作線程,如果線程池還沒有完全終止,仍需要保持一定數量的線程。
      1. 如果當前線程是突然終止的,調用addWorker()創建工作線程
      2. 當前線程不是突然終止,但當前工作線程數量小於線程池需要維護的線程數量,則創建工作線程。需要維護的線程數量爲corePoolSize(取決於成員變量 allowCoreThreadTimeOut是否爲 false)或1。
  • 源碼詳讀:

/**
 * Performs cleanup and bookkeeping for a dying worker. Called
 * only from worker threads. Unless completedAbruptly is set,
 * assumes that workerCount has already been adjusted to account
 * for exit.  This method removes thread from worker set, and
 * possibly terminates the pool or replaces the worker if either
 * it exited due to user task exception or if fewer than
 * corePoolSize workers are running or queue is non-empty but
 * there are no workers.
 *
 * @param w the worker
 * @param completedAbruptly if the worker died due to user exception
 */
private void processWorkerExit(Worker w, boolean completedAbruptly) {
    /**
     * 1.工作線程-1操作
     * 1)如果completedAbruptly 爲true,說明工作線程發生異常,那麼將正在工作的線程數量-1
     * 2)如果completedAbruptly 爲false,說明工作線程無任務可以執行,由getTask()執行worker-1操作
     */
    if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
        decrementWorkerCount();

    // 2.從線程set集合中移除工作線程,該過程需要加鎖
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        // 將該worker已完成的任務數追加到線程池已完成的任務數
        completedTaskCount += w.completedTasks;
        // HashSet<Worker>中移除該worker
        workers.remove(w);
    } finally {
        mainLock.unlock();
    }
    
	// 3.根據線程池狀態進行判斷是否結束線程池
    tryTerminate();
	
	/**
     * 4.是否需要增加工作線程
     * 線程池狀態是running 或 shutdown
     * 如果當前線程是突然終止的,addWorker()
     * 如果當前線程不是突然終止的,但當前線程數量 < 要維護的線程數量,addWorker()
     * 故如果調用線程池shutdown(),直到workQueue爲空前,線程池都會維持corePoolSize個線程,然後再逐漸銷燬這corePoolSize個線程
     */
    int c = ctl.get();
    if (runStateLessThan(c, STOP)) {
       if (!completedAbruptly) {
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            if (min == 0 && ! workQueue.isEmpty())
                min = 1;
            if (workerCountOf(c) >= min)
                return; // replacement not needed
        }
        addWorker(null, false);
    }
}

3.5 線程初始化

默認情況下,創建線程池之後,線程池中是沒有線程的,需要提交任務之後纔會創建線程。
在實際中如果需要線程池創建之後立即創建線程,可以通過以下兩個方法辦到:

  • prestartCoreThread():boolean prestartCoreThread(),初始化一個核心線程
  • prestartAllCoreThreads():int prestartAllCoreThreads(),初始化所有核心線程,並返回初始化的線程數
public boolean prestartCoreThread() {
    return addIfUnderCorePoolSize(null); //注意傳進去的參數是null
}

public int prestartAllCoreThreads() {
    int n = 0;
    while (addIfUnderCorePoolSize(null))//注意傳進去的參數是null
        ++n;
    return n;
}

注意上面傳進去的參數是null,根據第2小節的分析可知如果傳進去的參數爲null,則最後執行線程會阻塞在getTask方法中的

r = workQueue.take();

即等待任務隊列中有任務。

3.6 任務緩存隊列及排隊策略

在前面我們多次提到了任務緩存隊列,即workQueue,它用來存放等待執行的任務。workQueue的類型爲BlockingQueue,通常可以取下面三種類型:

  • ArrayBlockingQueue:基於數組的先進先出隊列,此隊列創建時必須指定大小
  • LinkedBlockingQueue:基於鏈表的先進先出隊列,如果創建時沒有指定此隊列大小,則默認爲Integer.MAX_VALUE;
  • synchronousQueue:這個隊列比較特殊,它不會保存提交的任務,而是將直接新建一個線程來執行新來的任務。

3.6 線程池的關閉

ThreadPoolExecutor提供了兩個方法,用於線程池的關閉,分別是shutdown()和shutdownNow(),其中:

  • shutdown():不會立即終止線程池,而是要等所有任務緩存隊列中的任務都執行完後才終止,但再也不會接受新的任務
  • shutdownNow():立即終止線程池,並嘗試打斷正在執行的任務,並且清空任務緩存隊列,返回尚未執行的任務

3.7 線程池容量的動態調整

ThreadPoolExecutor提供了動態調整線程池容量大小的方法:setCorePoolSize()和setMaximumPoolSize(),

  • setCorePoolSize:設置核心池大小
  • setMaximumPoolSize:設置線程池最大能創建的線程數目大小

當上述參數從小變大時,ThreadPoolExecutor進行線程賦值,還可能立即創建新的線程來執行任務。

3.8 任務拒絕策略

當線程池的任務緩存隊列已滿並且線程池中的線程數目達到maximumPoolSize,如果還有任務到來就會採取任務拒絕策略,通常有以下四種策略:

  • ThreadPoolExecutor.AbortPolicy:丟棄任務並拋出RejectedExecutionException異常。
  • ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,但是不拋出異常。
  • ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,然後重新嘗試執行任務(重複此過程)
  • ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章