【Java】聊聊線程池,ThreadPoolExecutor源碼詳解

一、線程池起步

1. 線程池的基本介紹

首先Java裏的線程利用的線程模型是KLT,這帶來了許多好處,比如線程的阻塞不會帶來進程的阻塞,能更加高效地利用CPU的資源等。但這也意味着在Java裏的線程的創建和銷燬是一個相對偏且消耗資源的操作,Java線程依賴於內核線程,創建線程需要進行操作系統狀態切換,爲避免資源過度消耗需要設法重用線程執行多個任務。

線程池就起到這麼一個重用,它負責了線程緩存,也負責對線程進行統一的分配、調優與監控。通過多個任務重用線程,實現線程複用的效果,最終減少系統開銷;

1.1 什麼時候使用線程池?

  • 單個任務處理時間比較短;
  • 需要處理的任務數量很大;

1.2 線程池的優勢

  • 重用存在的線程,減少線程創建、消亡的開銷,提高性能;
  • 提高響應速度,當任務到達時,任務可以不需要等待線程創建就能立即執行;
  • 提高線程的可管理性,可統一分配,調優和監控;

2. Java的線程池相關類

二、Executor框架接口

Executor框架是一個用戶級的調度器,用以執行策略調用,執行和控制,目的是提供一種將“任務執行”與“任務運行”分離開來的機制;

Executor框架中最爲相關的接口有以下三個:

  • Executor:接口類,它是Executor框架的基礎,將任務的提交與任務的執行分離開來;
  • ExecutorService:擴展了 Executor 接口,添加了一些用來管理執行器生命週期和任務的聲明週期的相關方法;
  • ScheduledExecutorService:擴展了ExecutorService接口,支持在給定的延遲後運行命令,或者定期執行命令;

1. Executor接口

public interface Executor {

    /**
     * Executes the given command at some time in the future.  The command
     * may execute in a new thread, in a pooled thread, or in the calling
     * thread, at the discretion of the {@code Executor} implementation.
     */
    void execute(Runnable command);
}

Executor接口中只有一個函數,用來替代我們原先啓動線程的方式,我們可以簡單看下對比:

// ===:原先線程啓動方式
Thread t = new Thread();
t.start();

// ===:將任務交由Executor去處理
Thread t = new Thread();
executor.executor(t);

最終Executor會將任務交由具體的實現類處理,不同的實現類處理在不同情況下處理的方式不同,可能是立即創建一個新線程並啓動,也有可能是使用已有的工作線程來運行傳入的任務,也可能是根據設置線程池的容量或者阻塞隊列的容量來決定是否要將傳入的線程放入阻塞隊列中或者拒絕接收傳入的線程。

2. ExecutorService接口

ExecutorService擴展了Executor接口,是一個比 Executor 使用更廣泛的子類接口,其提供了生命週期管理的方法;調用ExecutorService的shutdown()方法可以平滑地關閉 ExecutorService,調用該方法後,將導致 ExecutorService 停止接受任何新的任務且等待已經提交的任務執行完成(已經提交的任務會分兩類:一類是已經在執行的,另一類是還沒有開始執行的),當所有已經提交的任務執行完畢後將會關閉 ExecutorService。如果需要支持即時關閉,也就是shutDownNow()方法,此時我們需要考慮任務中斷的處理。

3. ScheduledExecutorService接口

ScheduledExecutorService 擴展 ExecutorService 接口並增加了 schedule()scheduleAtFixedRate()等方法。調用 schedule() 方法可以在指定的延時後執行一個 Runnable 或者Callable 任務。調用 scheduleAtFixedRate()方法和scheduleWithFixedDelay()方法可以按照指定時間間隔定期執行任務。

三、ThreadPoolExecutor解析

ThreadPoolExecutor類實現 Executor 框架中最爲核心的一個類,它是線程池的實現類,接下來介紹下它的主要實現代碼:

1. ThreadPoolExecutor的參數

首先來看類中定義的幾個關鍵字段,如下:

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

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

這裏的ctl是用來保存線程池運行狀態(runState)和線程池內有效線程數量(workerCount)的一個字段,聲明爲一個 AtomicInteger 對象,主要包括了兩部分信息:高3位保存運行狀態,低29位保存workerCount。COUNT_BITS 就是29,CAPACITY就是1左移29位再 - 1,這個常量表示 workerCount 的最大值,即2的29次方。

接下來定義的幾個字段用來表示線程池的狀態,一個有五種狀態,這裏做一個簡單的說明:

  • RUNNING:能接受新提交的任務,以及對已添加的任務進行處理;
  • SHUTDOWN:處於shutdown狀態下的線程池不能接收新的任務,但能處理已經提交的任務;
  • STOP:線程池處於此狀態下,不接收新的任務,也不會處理已經提交的任務,會將已經提交的任務中斷;
  • TIDYING:當所有的任務已終止,ctl記錄的有效線程數量爲0時,線程池會變爲TIDYING狀態。當線程池變爲TIDYING狀態時,會執行鉤子函數terminated(),該函數在 ThreadPoolExecutor 類中是空的,若用戶想在線程池變爲 TIDYING 時,進行相應的處理,可以通過重載該函數來實現;
  • TERMINATEDterminated() 方法被執行完後進入該狀態,線程池徹底終止,變成TERMINATED狀態。

2. execute函數

首先我們先來了解ThreadPoolExecutor執行execute()方法的示意圖:

通過上圖我們能瞭解,線程池有三個梯度,分別是核心線程池、等待隊列、整個線程池,這三個梯度在代碼的執行流程中發揮了重要的作用,接下來我們來了解它的源碼執行過程,如下所示:

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();

    // 獲取clt,即獲取runState和workerCount 
    int c = ctl.get();

    // workerCountOf(c)取出低29位的值即當前活動的線程數,如果小於corePoolSize,
    // 則新建一工作線程放入線程池中,並將該線程用來執行當前任務
    if (workerCountOf(c) < corePoolSize) {

        // addWorker中的第二個參數表示限制添加線程的數量是根據 corePoolSize 來判斷還是 maximumPoolSize 來判斷;
        // 如果爲true,根據corePoolSize來判斷;如果爲false,則根據maximumPoolSize來判斷
        if (addWorker(command, true))
            // 添加成功直接返回
            return;
        // 如果添加失敗,則重新獲取ctl值
        c = ctl.get();
    }

    // 如果當前線程池是運行狀態並且任務添加到隊列成功
    if (isRunning(c) && workQueue.offer(command)) {
        // 重新獲取ctl值
        int recheck = ctl.get();
        // 再次判斷線程池的運行狀態,若不是運行狀態,由於之前已經把command添加到隊列中了,現在需要移除該command 
        if (!isRunning(recheck) && remove(command))
            // 使用拒絕策略對該任務進行處理
            reject(command);

        // 獲取線程池中的有效線程數,如果數量是0,則執行addWorker方法
        // 如果判斷workerCount大於0,則直接返回,因爲任務已經加入到隊列中了,之後會被調度執行,這裏不操心、。
        else if (workerCountOf(recheck) == 0)
            // 添加一個新的工作線程,任務已經在工作隊列裏了,所以第一個參數爲null
            addWorker(null, false);
    }

    /*
     * 代碼如果走到這,那麼有以下兩種情況:
     * 	1. 線程池已經不是RUNNING狀態了;
     * 	2. 線程池是RUNNING狀態,但現有線程>=核心線程池的數量,並且Blocking隊列已滿,
     *  那麼線程到第三個梯度,整個線程池了,這時,再次調用addWorker方法,
     *  第二個參數傳入爲false,將線程池的有限線程數量設置爲maximumPoolSize;
     */
    else if (!addWorker(command, false))
        // 如果失敗則拒絕該任務
        reject(command);
}

簡單來說,如果線程池一直處於 RUNNING 狀態的話,則函數的執行流程如下:

  1. 如果當前運行的線程少於 corePoolSize,則創建新線程來執行任務(注意,執行這一步驟需要獲取全局鎖,後面再介紹);
  2. 如果運行的線程等於或多於 corePoolSize,則將任務假如到 BlockingQueue;
  3. 如果無法將任務加入 BlockingQueue(隊列已滿),在創建新的線程來處理任務(注意,執行這一步需要獲取全局鎖);
  4. 如果創建新線程將使用當前運行的線程大於 maximumPoolSize,任務將被拒絕,並調用 RejectedExecutionHandler.reijectedExecution()方法;

ThreadPoolExecutor採取上述步驟的總體設計思路,是爲了在執行execute()方法時,儘可能避免記獲取全局鎖,那將會是一個很嚴重的可伸縮瓶頸)。在ThreadPoolExecutor完成預熱之後(當前運行的線程數大於大於corePoolSize),幾乎所有的 execute()方法調用都是通過執行步驟2,而步驟2不需要獲取全局鎖。

3. 構造函數

接下來我們來了解如何創建一個新的線程池,它的構造函數的源代碼如下:

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:定義核心線程的數量,當提交一個任務到線程池時,線程池會創建一個線程來執行任務,即使其他空閒線程的基本線程能夠執行新任務;
  • maximumPoolSize:定義線程池最大線程的數量;
  • keepAliveTime:線程池維護線程所允許的空閒時間。當線程池中的線程數量大於corePoolSize的時候,如果這時沒有新的任務提交,核心線程外的線程不會立即銷燬,而是會等待,直到等待的時間超過了keepAliveTime
  • unit:時間單位,爲 keepAliveTime 指定時間單位;
  • workQueue:等待隊列,當任務提交時,如果線程池中的線程數量大於等於corePoolSize的時候,把該任務封裝成一個Worker對象放入等待隊列,它有以下幾個實現類可以選擇:
    • ArrayBlockingQueue:是一個基於數組結構的有界阻塞隊列,此隊列按FIFO原則對元素進行排序;
    • LinkedBlockingQueue:一個基於鏈表結構的阻塞隊列,此隊列按FIFO排序元素,吞吐量要高於 ArrayBlockingQueue;
    • SynchronousQueue:一個不存儲元素的阻塞隊列。每個插入操作必須等到另一個線程調用移除操作,否則插入操作一直處於阻塞狀態;
    • PriorityBlockingQueue:一個具有優先級的無限阻塞隊列;
  • threadFactory:它是ThreadFactory類型的變量,用來創建新線程。默認使用Executors.defaultThreadFactory() 來創建線程。使用默認的ThreadFactory來創建線程時,會使新創建的線程具有相同的 NORM_PRIORITY 優先級並且是非守護線程,同時也設置了線程的名稱。
  • handler:它是 RejectedExecutionHandler 類型的變量,表示線程池的飽和策略。如果阻塞隊列滿了並且沒有空閒的線程,這時如果繼續提交任務,就需要採取一種策略處理該任務。線程池提供了4種策略:
    • AbortPolicy:直接拋出異常,這是默認策略;
    • CallerRunsPolicy:用調用者所在的線程來執行任務;
    • DiscardOldestPolicy:丟棄阻塞隊列中靠最前的任務,並執行當前任務;
    • DiscardPolicy:直接丟棄任務;

4. addWorker函數

上面我們講execute()函數時,提到了addWorker()函數,該函數的作用主要就是在線程池中新建一個新的線程並執行,firstTask參數表示指定新增的線程執行的第一個任務,core參數爲 true 表示在此時線程數量的上限爲corePoolSize,當這個參數爲 false 時,表示此時代碼判斷的線程數量上限爲maximunPoolSize,這一點在前面代碼已經有所提及。源代碼如下所示:

private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    for (;;) {
        int c = ctl.get();
        // 獲取運行狀態
        int rs = runStateOf(c);

        // 檢查隊列是否在必要的時候爲空
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;

        // 這一循環內主要就是通過CAS增加線程個數
        for (;;) {
            int wc = workerCountOf(c);
            // 如果線程數量超出限制,返回false
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            // CAS增加workerCount,如果成功,則跳出第一個for循環
            if (compareAndIncrementWorkerCount(c))
                break retry;
            // 如果增加workerCount失敗,則重新獲取ctl的值
            c = ctl.get(); 
            // 如果當前的運行狀態不等於rs,說明狀態已被改變,返回第一個for循環繼續執行
            if (runStateOf(c) != rs)
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
        }
    }

    // 執行到這裏說明CAS增加新線程個數成功了,下面開始要創建新的工作線程Worker

    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
        // 根據firstTask來創建 Worker對象
        w = new Worker(firstTask);
        // Worker對象內部維護着一個線程對象
        final Thread t = w.thread;
        if (t != null) {
            // 加鎖
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                // 重新檢查線程池狀態,以免在獲取鎖之前調用shutdown方法改變線程池狀態
                int rs = runStateOf(ctl.get());
                // rs < SHUTDOWN表示是 RUNNING 狀態;
                // 如果rs是RUNNING狀態或者rs是SHUTDOWN狀態並且 firstTask 爲null,向線程池中添加線程。
                // 因爲在 SHUTDOWN 時不會在添加新的任務,但還是會執行workQueue中的任務
                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
                    if (t.isAlive())
                        throw new IllegalThreadStateException();
                    // workers是一個HashSet
                    workers.add(w);
                    int s = workers.size();
                    // largestPoolSize記錄着線程池中出現過的最大線程數量
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                // 釋放鎖
                mainLock.unlock();
            }
            if (workerAdded) {
                // 啓動線程
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

上面代碼的最後幾行,會判斷添加工作線程是否成功,如果失敗,會執行addWorkerFailed()函數,將任務從workers中移除,並且做workerCount-1操作,簡單看一下該函數的源代碼:

private void addWorkerFailed(Worker w) {
    
    final ReentrantLock mainLock = this.mainLock;
    // 加鎖
    mainLock.lock();
    try {
        // 如果worker不爲null
        if (w != null)
            // workers移除worker
            workers.remove(w);
        // 通過CAS操作使 workerCount-1
        decrementWorkerCount();
        tryTerminate();
    } finally {
        // 釋放鎖
        mainLock.unlock();
    }
}

5. Worker對象

回顧上面代碼,我們發現線程池創建新的工作線程都是去創建一個新的 Worker 對象,事實上線程池中的每一個工作線程都被封裝爲Worker對象,ThreadPool 其實就是在維護着一組 Worker 對象,接下來我們來了解Worker類的源代碼,如下:

private final class Worker 
    extends AbstractQueuedSynchronizer 
    implements Runnable 
{
    
    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;
    // 給出初始firstTask,由線程創建工廠創建新的線程
    Worker(Runnable firstTask) {
        // 將狀態設置爲-1,防止被調用前就被中斷
        setState(-1);
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this);
    }
    /** Delegates main run loop to outer runWorker  */
    // 將run方法的運行委託給外部runWorker()函數
    public void run() {
        runWorker(this);
    }
    
    // 關於同步狀態(鎖)
    //
    // 同步狀態state=0表示鎖未被獲取
    // 同步狀態state=1表示鎖被獲取
    protected boolean isHeldExclusively() {
        return getState() != 0;
    }
    protected boolean tryAcquire(int unused) {
        if (compareAndSetState(0, 1)) {
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        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); }
    public boolean isLocked() { return isHeldExclusively(); }
    void interruptIfStarted() {
        Thread t;
        if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
            try {
                t.interrupt();
            } catch (SecurityException ignore) {
            }
        }
    }
}

Worker類繼承了AQS,並實現了Runnable接口,其中 firstTask 屬性用來保存傳入的任務,而 thread 屬性是在調用構造方法時傳入的 ThreadFactory 創建出來的線程,是用來處理任務的線程。

在調用構造方法時,需要把任務傳入,這裏通過getThreadFactory().newThread(this);來新建一個線程,newThread() 方法傳入的參數是 this;因爲 Worker 本身繼承了 Runnable 接口,也就是一個線程,所以一個 Worker 對象在啓動的時候會調用 Worker 類中的 run 方法。而run方法中又調用了runWorker()方法。

6. runWorker函數

通過上面源碼的瞭解,我們知道任務最終的執行是在runWorker()函數中,接下來我們就通過源碼來了解這一過程:

final void runWorker(Worker w) {
    
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    // 調用unlock(),將state設置爲0,允許中斷
    w.unlock();
    // 是否因爲異常退出循環
    boolean completedAbruptly = true;
    try {
        // 如果task爲空,則通過getTask()從隊列中獲取任務
        while (task != null || (task = getTask()) != null) {
            // 加鎖
            w.lock();
            // 如果線程池已被停止(STOP)(至少大於STOP狀態),要確保線程都被中斷
            // 如果狀態不對,檢查當前線程是否中斷並清除中斷狀態,並且再次檢查線程池狀態是否大於STOP
            // 如果上述滿足,檢查該對象是否處於中斷狀態,不清除中斷標記
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                // 中斷該對象
                wt.interrupt();
            try {
                beforeExecute(wt, task);
                Throwable thrown = null;
                try {
                    // 執行任務
                    task.run();
                } catch (RuntimeException x) {
                    thrown = x; throw x;
                } catch (Error x) {
                    thrown = x; throw x;
                } catch (Throwable x) {
                    thrown = x; throw new Error(x);
                } finally {
                    afterExecute(task, thrown);
                }
            } finally {
                task = null;
                // 更新當前已經完成任務數量
                w.completedTasks++;
                // 釋放鎖
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        // 執行清理工作,處理並退出當前 worker
        processWorkerExit(w, completedAbruptly);
    }
}

看完上面代碼,我們可以大致地瞭解runWorker()函數的執行流程:

  1. 首先執行unlock()方法,將Worker的state置爲0這樣工作線程就可以被中斷了(因爲後續線程池關閉操作需要去中斷線程);
  2. 判斷當前工作的任務(當前工作線程中的task,或者從任務隊列中取出的task)是否爲null,如果不爲null就往下執行,爲null就執行processWorkerExit()方法;
  3. 獲取工作線程內部持有的獨佔鎖(避免在執行任務期間,其他線程調用shutdown後正在執行的任務被中斷,shutdown只會中斷當前被阻塞掛起的沒有執行任務的線程);
  4. 之後執行beforeExecute()函數,該方法爲擴展接口代碼,表示在具體執行任務之前出一些處理,然後就開始執行task.run()函數去真正地執行具體任務,執行完之後會調用afterExecute()方法,用以處理任務執行完畢之後的工作,也是一個擴展接口代碼;
  5. 更新當前線程池完成的任務數,並釋放鎖;

7. getTask函數

上面runWorker()函數中我們稍微提及了getTask()函數,該函數表示從阻塞隊列中獲取任務,接下來我們就來進行了解,其源代碼如下:

private Runnable getTask() {
    // timeOut變量的值表示上次從阻塞隊列中取任務時是否超時
    boolean timedOut = false;
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);
        
        // 如果線程池處於非RUNNING狀態或者處於STOP以上狀態並且等待隊列爲空
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            // 將workCount-1並返回null
            decrementWorkerCount();
            return null;
        }
        int wc = workerCountOf(c);
   
        // timed 變量用於判斷是否需要進行超時控制。
        // allowCoreThreadTimeOut默認是false,也就是核心線程不允許進行超時;
        // wc > corePoolSize,表示當前線程池中的線程數量大於核心線程數量;
        // 對於超過核心線程數量的這些線程,需要進行超時控制
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        /*
         * wc > maximumPoolSize的情況是因爲可能在此方法執行階段同時執行了setMaximumPoolSize方法;
         * timed && timedOut 如果爲true,表示當前操作需要進行超時控制,並且上次從阻塞隊列中獲取任務發生了超時
         * 接下來判斷,如果有效線程數量大於1,或者阻塞隊列是空的,那麼嘗試將workerCount減1;
         * 如果減1失敗,則返回重試。
         * 如果wc == 1時,也就說明當前線程是線程池中唯一的一個線程了。
         */
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }
        try {
            /*
             * 根據timed來判斷,如果爲true,則通過阻塞隊列的poll方法進行超時控制,如果在keepAliveTime時間內沒有獲取到任務,則返回null;
             * 否則通過take方法,如果這時隊列爲空,則take方法會阻塞直到隊列不爲空。
             */
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
            workQueue.take();
            if (r != null)
                return r;
            // 如果 r == null,說明已經超時,timedOut設置爲true
            timedOut = true;
        } catch (InterruptedException retry) {
            // 如果獲取任務時當前線程發生了中斷,則設置timedOut爲false並返回循環重試
            timedOut = false;
        }
    }
}

這裏主要講一下第二個 if 判斷,這段代碼的目的是爲了控制線程池的有效線程數量:

由上文中的分析可以知道,在執行execute方法時,如果當前線程池的線程數量超過了corePoolSize 且小於 maximumPoolSize,並且 workQueue 已滿時,則可以增加工作線程,但這時如果超時沒有獲取到任務,也就是 timedOut 爲true的情況,說明 workQueue 已經爲空了,也就說明了當前線程池中不需要那麼多線程來執行任務了,可以把多於corePoolSize數量的線程銷燬掉,保持線程數量在corePoolSize即可。

什麼時候會銷燬?當然是runWorker()方法執行完之後,也就是Worker中的run方法執行完,由 JVM 自動回收。getTask()函數返回null時,在runWorker()方法中會跳出while循環,最後會執行processWorkerExit()方法。

8. processWorkerExit函數

private void processWorkerExit(Worker w, boolean completedAbruptly) {
    
    // 如果completedAbruptly值爲true,則說明線程執行時出現了異常,需要將workerCount減1;
    if (completedAbruptly)
        decrementWorkerCount();
    
    // 獲取全局鎖
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        // 統計完成的任務數
        completedTaskCount += w.completedTasks;
        // 從 workers 中移除一個工作線程
        workers.remove(w);
    } finally {
        // 釋放鎖
        mainLock.unlock();
    }
    
    // 根據線程池狀態判斷是否結束線程池
    tryTerminate();
    
    int c = ctl.get();
    
    // 判斷當前線程池中的數量是否少於核心線程數
    if (runStateLessThan(c, STOP)) {
        if (!completedAbruptly) {
            // allowCoreThreadTimeOut表示是否允許核心線程超時,默認爲false
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            // 如果min等於0並且隊列不爲空
            if (min == 0 && ! workQueue.isEmpty())
                min = 1;
            // 現有工作線程大於min,直接返回
            if (workerCountOf(c) >= min)
                return;
        }
        addWorker(null, false);
    }
}

我們大致來縷清下processWorkerExit()函數的工作流程:

  1. 首先獲取全局鎖,之後對線程池完成的任務個數進行統計,之後再從工作線程的集合中移除當前工作線程,完成清理工作;
  2. 調用tryTerminate()函數,根據線程池狀態判斷是否結束線程池,下面詳細講該函數實現;
  3. 判斷當前線程池中的線程個數是否小於核心線程數,如果是就新增一個線程保證有足夠的線程可以執行任務隊列中的任務或者提交的新任務;

9. tryTerminate方法

tryTerminate()方法的主要工作就是根據線程池狀態進行判斷是否結束線程池,代碼如下:

final void tryTerminate() {
    for (;;) {
        int c = ctl.get();
        /*
         * 當線程池的狀態爲以下幾種情況時,直接返回,不調用terminated():
         * RUNNING:線程池還在運行中,不能停止去terminated;
         * TIDYING或TERMINATED:因爲線程池中已經沒有正在運行的線程了;
         * SHUTDOWN並且等待隊列非空:需要先執行完workQueue中的task;
         */
        if (isRunning(c) ||
            runStateAtLeast(c, TIDYING) ||
            (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
            return;
        
        // 如果線程數量不爲0,則中斷一個空閒的工作線程,並返回
        if (workerCountOf(c) != 0) {
            interruptIdleWorkers(ONLY_ONE);
            return;
        }

        // 加鎖
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            // 通過CAS嘗試設置狀態爲TIDYING,如果設置成功,則調用terminated方法
            if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                try {
                    // 調用terminated()方法
                    terminated();
                } finally {
                    // 設置狀態爲TERMINATED
                    ctl.set(ctlOf(TERMINATED, 0));
                    termination.signalAll();
                }
                return;
            }
        } finally {
            // 解鎖
            mainLock.unlock();
        }
        // else retry on failed CAS
    }
}

這裏需要注意的一點是,terminated()默認什麼都不實現,需要繼承類根據業務場景去完成該方法;

後面再繼續補充和修改,有錯誤請評論區指正。

文章信息來源:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章