【線程池】重要方法底層原理解讀

分析線程池的底層原理,有問題煩請批評指正。

execute()方法

提交任務command到線程池執行。

public void execute(Runnable command) {
    // 0. 如果任務爲空,拋出NPE
    if (command == null)
        throw new NullPointerException();

    // 獲取ctl屬性=線程狀態+線程數
    int c = ctl.get();
    // 1. 如果當前池中線程數量小於核心線程數
    if (workerCountOf(c) < corePoolSize) {
        // 向workers新增核心線程執行command任務
        if (addWorker(command, true))
            return;
        // 添加線程失敗
        c = ctl.get();
    }
    // 2. 如果池是RUNNING狀態,並且可以添加任務到阻塞隊列
    if (isRunning(c) && workQueue.offer(command)) {
        // 二次校驗:可能在添加任務到隊列後,
        // 不再需要添加一個線程(過程中有線程死亡)或者線程池關閉,池狀態發生改變。
        int recheck = ctl.get();
        // 2.1 如果線程池已關閉,則刪除隊列任務【巧妙的寫法 !false && remove()】
        if (!isRunning(recheck) && remove(command))
            // 執行拒絕策略
            reject(command);
        // 2.2 否則如果池中線程數爲0,則新增一個線程
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    // 3. 如果隊列已滿,則新增線程;如果新增失敗,就執行拒絕策略。
    else if (!addWorker(command, false))
        reject(command);
}

上面代碼執行的步驟使用文字來敘述一下。

1 如果當前工作線程數量,小於核心線程的數量,就嘗試添加一個新線程,並把傳入的command作爲新添加的線程的第一個任務,添加成功就返回。調用addWorker方法會以原子方式檢查runState和workerCount並且通過返回false來避免在假喚醒的時候添加線程。

2 如果當前線程池正在運行並且任務可以成功加入到隊列中,我們仍然需要再次檢查線程池(檢查線程池的運行狀態,當前線程池中的工作線程數量)。爲什麼要再次檢查呢?

當任務被添加到了阻塞隊列前,線程池處於RUNNING 狀態,但如果在添加到隊列成功後,線程池進入了SHUTDOWN 狀態或者其他狀態,這時候是不應該再接收新的任務的,所以需要把這個任務從隊列中移除,並且拒絕該任務。同樣,在沒有添加到隊列前,可能有一個有效線程,但添加完任務後,這個線程可能因爲閒置超時或者異常被幹掉了,這時候需要創建一個新的線程來執行任務。

2.1 如果線程池狀態線程池是SHUTDOWN了,並且能成功的把任務從隊列中移除,就拒絕任務。
2.2 如果任務加入到隊列了,但是當前沒有工作線程就添加一個線程。

3 如果我們不能把任務加入隊列,那麼我們嘗試添加一個新線程,如果添加失敗(線程池已經shut down 或者已經飽和了),就拒絕任務。

所謂拒絕,就是調具體拒絕策略的rejectedExecution()方法。

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

addWorker方法

其中addWorker是execute()方法的核心方法,用於原子校驗線程池狀態和線程數,並新增線程。

private boolean addWorker(Runnable firstTask, boolean core) {
    // 跳轉標籤
    retry:
    // 外層循環
    for (;;) {
        // 獲取ctl屬性與線程池狀態
        int c = ctl.get();
        int rs = runStateOf(c);

        // 1. 線程池狀態校驗,僅在必要時檢查隊列是否爲空。
        // 返回false總結
        // 1.1 線程池是STOP、TIDYING、TERMINATED
        // 2.2 線程池是SHUTDOWN,並且任務不爲null或阻塞隊列爲空
        if (rs >= SHUTDOWN && // 線程池非RUNNING狀態
            ! (rs == SHUTDOWN && // 特殊情況:池狀態爲SHUTDOWN,任務爲null,工作隊列不爲空
               firstTask == null &&
               ! workQueue.isEmpty())) 
            return false;
        // 內層循環,CAS增加線程個數
        for (;;) {
            int wc = workerCountOf(c);
            // 2. 當前線程個數超過上限,返回false
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            // 3. CAS成功增加線程數,直接退出外層循環
            if (compareAndIncrementWorkerCount(c))
                break retry;
            c = ctl.get();  // 再次讀取ctl值
            // 如果CAS增加線程失敗,校驗線程池狀態是否發生改變
            // 4. 如果池狀態改變,跳到外層循環,重新獲取池狀態;
            if (runStateOf(c) != rs)
                continue retry;
            // 未改變,則內層循環再次進行CAS操作
        }
    }

    // 此時完成CAS操作,線程數增加
    
    // 5. 添加worker的邏輯
    boolean workerStarted = false; // 標記工作線程是否啓動
    boolean workerAdded = false; // 標記worker是否添加到workers
    Worker w = null; // 工作線程
    try {
        // 創建worker
        w = new Worker(firstTask);
        final Thread t = w.thread;
        if (t != null) { 
            // 此時,線程工廠成功創建了一個線程
            final ReentrantLock mainLock = this.mainLock; // 全局獨佔鎖-mainLock
            // 加鎖:實現worker添加操作同步
            mainLock.lock(); 
            try {
                // 再次檢查線程池狀態。ThreadFactory失敗或者獲取鎖前shutdown,退出
                int rs = runStateOf(ctl.get());
                // 線程池狀態爲RUNNING,或者池狀態爲SHUTDOWN,並且任務爲null時
                if (rs < SHUTDOWN ||  
                    (rs == SHUTDOWN && firstTask == null)) { 
                    // 如果線程已經啓動,拋出異常
                    if (t.isAlive()) 
                        throw new IllegalThreadStateException();
                    // 添加worker到線程池workers線程集合
                    workers.add(w);
                    int s = workers.size();
                    // 改變最大線程數,池大小動態變化
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true; // 更新任務添加標識
                }
            } finally {
                // 解除mainLock鎖
                mainLock.unlock(); 
            }
            // 任務添加後,啓動線程
            if (workerAdded) {
                t.start();
                workerStarted = true; // 更新線程啓動標識
            }
        }
    } finally {
        // 如果線程啓動失敗,回滾worker創建過程
        if (! workerStarted)
            addWorkerFailed(w);
    }
    // 返回任務是否成功啓動
    return workerStarted;
}

檢查根據當前池狀態和給定的邊界(核心或者最大)是否可以添加新worker。

代碼主要分爲兩部分,一部分是通過CAS增加線程數,另一部分是加鎖添加任務到workers,並啓動線程。

如果線程池關閉、線程工廠創建失敗(線程工廠返回空,或異常(線程創建時OOM)),會返回false,並回滾上面的操作。

回滾過程,包括

  • 從workers刪除任務;
  • CAS減少線程數
  • 重新校驗終止,避免該worker的存在阻礙了終止。
private void addWorkerFailed(Worker w) {
    // 獲取全局鎖
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        if (w != null)
            // 刪除worker任務
            workers.remove(w);
         // CAS較少線程數
        decrementWorkerCount();
        // 嘗試終止
        tryTerminate();
    } finally {
        mainLock.unlock(); // 解鎖
    }
}

Woker嵌套類–Worker線程類

Worker類實現Runnable接口,用來具體承載任務;繼承AQS,自己實現不可重入的獨佔鎖。鎖狀態state有3個:-1表示創建時狀態,0表示鎖未獲取狀態,1表示鎖已獲取狀態。

private final class Worker extends AbstractQueuedSynchronizer implements Runnable
{
    // 執行當前任務的線程,如果爲null說明線程工廠添加失敗
    final Thread thread; // final修飾
    // 要執行的任務,可能爲null
    Runnable firstTask;
    // 每個線程的任務計數器,標記當前線程執行了多少個任務
    volatile long completedTasks;
    
    Worker(Runnable firstTask) {
        setState(-1); // runWorker執行前禁止中斷
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this); // 創建一個線程
    }
    // Worker實現run方法
    public void run() {
        runWorker(this);
    }
}

worker在run方法中調用了ThreadPoolExecutor的runWorker方法,並傳入worker實例自身。

runWorker

runWorker執行前禁止中斷,是因爲shutdownNow()方法中在中斷Worker時會對state做校驗,state>=0纔會去中斷線程。

inal void runWorker(Worker w) {
    // 當前線程即worker中的線程
    Thread wt = Thread.currentThread(); 
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock(); // 設置state爲0,運行中斷
    boolean completedAbruptly = true; // 快速結束標識
    try { // 第1層trycatch
        // 校驗firstTask和阻塞隊列中任務是否爲空
        while (task != null || (task = getTask()) != null) {
            w.lock(); // 加鎖

            // 如果調用了shutdownNow()方法,池是STOP,確保線程被中斷
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt(); // 中斷線程
            try { // 第2層trycatch
                // 執行任務前,執行鉤子方法
                beforeExecute(wt, task);
                Throwable thrown = null;
                try { // 第3層trycatch
                    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; // 清理worker中的任務,下次循環獲取新任務
                w.completedTasks++;
                w.unlock();
            }
        } // while循環結束
        completedAbruptly = false;
    } finally {
        // 清理worker
        processWorkerExit(w, completedAbruptly);
    }
}
  1. while 循環,如果Worker的firstTask不爲null,或者getTask() 從隊列中獲取任務不爲null。注意從隊列中獲取任務可能會阻塞。如果Worker能夠取到任務,就不停的執行,這就是爲什麼線程可以複用不退出的原因。
    任務分爲兩種,一種是創建Woker的那個firstTask,另一種是阻塞隊列中的任務。
    如果兩種任務均爲null,或者線程池狀態改變,completedAbruptly快速結束標識會保持爲true,進行worker清理操作。
  2. 在適當的時候中斷Worker的工作線程wt。
  3. 執行任務。
    執行任務時加鎖,避免任務運行時,被其他線程調用shutdown中斷。除非線程池是STOPPING狀態,否則不會中斷線程。
    beforeExecute()方法在第2層trycatch內,如果拋出異常退出,任務不再執行,只會執行後面的兩層finally操作。completedAbruptly值仍爲true,清理worker線程。
    任何異常,都會導致清理線程。
  4. 如果沒有可執行的任務,或者異常終止則Worker停止工作,Worker的工作線程也就結束了。

processWorkerExit()方法用來清理將要死亡的線程。

private void processWorkerExit(Worker w, boolean completedAbruptly) {
    // 1. 如果是突然退出,CAS減少線程數
    if (completedAbruptly) 
        // 只在此處和getTask()方法中調用!!
        decrementWorkerCount(); 

    // 2. 統計線程池完成的線程數,並從工作線程集中移除當前Worker
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        completedTaskCount += w.completedTasks;
        workers.remove(w);
    } finally {
        mainLock.unlock();
    }
    // 3. 試圖終止線程池
    tryTerminate();
    
    // 獲取ctl屬性
    int c = ctl.get();
    // 4. 如果線程池未終止,判斷是否新增worker
    // 線程池狀態是RUNNING或者SHUTDOWN
    if (runStateLessThan(c, STOP)) {
        if (!completedAbruptly) { 
            // 如果worker不是突然退出
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            if (min == 0 && ! workQueue.isEmpty())
                min = 1;
            if (workerCountOf(c) >= min)
                return; // replacement not needed
        }
        // 4.1 如果是突然退出,直接添加新worker
        // 4.2 如果正在運行的線程少於corePoolSize,則新增worker
        // 4.3 如果此時阻塞隊列不爲空,但是工作線程數爲0,則新增worker
        addWorker(null, false);
    }
}

getTask()方法用來從阻塞隊列中獲取任務

// 執行阻塞或者定時等待任務
private Runnable getTask() {
    boolean timedOut = false; // 最後的poll操作是否超時
    // 死循環
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c); // 線程池狀態
        
        // 1. 循環退出條件1:線程狀態
        // 1.1 線程池是STOP、TIDYING、TERMINATED狀態
        // 1.2 線程池非RUNNING狀態,並且任務隊列爲空,返回null
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            // 減少workerCount數
            decrementWorkerCount();
            return null;
        }
        // 當前池中woker數
        int wc = workerCountOf(c);

        // 是否結束當前woker
        // allowCoreThreadTimeOut默認爲false(核心線程即使空閒,仍然可以存活,即不使用超時)
        // coreThread能夠超時,或者當前woker數大於corePoolSize時,當前woker可以超時
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        // 2.  循環退出條件2:線程數
        // 2.1 worker的數量大於maximumPoolSize
        // 2.2 worker等待從隊列中獲取任務時超時
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            // 減少worker數,並返回null
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }

        try {
            Runnable r = timed ? // 是否可超時
                // 如果可以超時,使用poll方法,在keepAliveTime時間內獲取任務【keepAliveTime超時的使用】
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                // 不可以超時,則一直等待直到獲取到任務爲止【核心線程不退出的原因】
                workQueue.take();
            if (r != null) 
                // 成功獲取到任務,直接返回
                return r;
            // 走到這一步,說明從任務隊列中獲取任務超時了。
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

方法只有兩種結果,一種是成功獲取到任務,另一種是當前worker必須退出時返回null,此時減少wokerCount數。

當前worker在遇到以下原因時必須停止返回null。

  1. worker的數量大於maximumPoolSize
  2. 線程池大於等於STOP狀態
  3. 線程池是SHUTDOWN狀態,並且阻塞隊列沒有任務
  4. worker等待從隊列中獲取任務時超時【着重理解】

最最核心的代碼

Runnable r = timed ? // 是否可超時
        // 如果可以超時,使用poll方法,在keepAliveTime時間內獲取任務【keepAliveTime超時的使用】
        workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
        // 不可以超時,則一直等待直到獲取到任務爲止【核心線程不退出的原因】
        workQueue.take();

shutdown方法–溫柔的關閉方式

public void shutdown() {
    // 獲取全局鎖--方法中有鎖重入
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        // 1. 權限校驗
        checkShutdownAccess();
        // 2. 校驗狀態。如果狀態大於等於SHUTDOWN,直接返回;否則設置線程池狀態爲SHUTDOWN
        advanceRunState(SHUTDOWN);
        // 3. 中斷空閒線程--getTask方法中正在等待任務的線程
        interruptIdleWorkers();
        onShutdown(); // ScheduledThreadPoolExecutor的hook方法
    } finally {
        mainLock.unlock();
    }
    // 嘗試將線程狀態轉變爲TERMINATED
    tryTerminate();
}

執行一次順序關閉,執行以前提交的任務,並且拒絕新任務。

爲什麼可以中斷空閒線程?
任務隊列是Java併發工具中的阻塞隊列,其take和poll方法可以自動響應中斷。

java.util.concurrent.BlockingQueue.take()/put(E)

tryTerminate()

final void tryTerminate() {
    for (;;) {
        int c = ctl.get();
        // 線程池狀態以及woker線程數校驗。不符合直接返回

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            // 設置池狀態爲TIDYING
            if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                try {
                    // 調用線程池的hook方法
                    terminated();
                } finally {
                    // 設置線程池狀態爲TERMINATED
                    ctl.set(ctlOf(TERMINATED, 0));
                    // 通知因爲調用termination變量await方法而阻塞的所有線程。
                    termination.signalAll();
                }
                return;
            }
        } finally {
            mainLock.unlock();
        }
        // else retry on failed CAS
    }
}

關於termination變量,是mainLock的線程交互對象。Condition是將 Object 監視器方法(wait、notify 和 notifyAll)分解成截然不同的對象。

private final Condition termination = mainLock.newCondition();

isShutDown()方法–判斷是否執行過shutdown命令

public boolean isShutdown() {
    return ! isRunning(ctl.get());
}

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

判斷線程池是否已關閉,狀態大於等於SHUTDOWN。

shutdownNow()方法–不溫柔的關閉方式

試圖停止所有正在執行的線程,暫停處理阻塞隊列中的任務,並返回一個等待執行任務的列表。不能保證終止線程,任務可能沒有響應中斷【不是強制中斷】。

public List<Runnable> shutdownNow() {
    List<Runnable> tasks;
    // 獲取鎖
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        // 1. 權限校驗
        checkShutdownAccess();
        // 2. 校驗狀態。如果狀態大於等於STOP,直接返回;否則設置線程池狀態爲STOP
        advanceRunState(STOP);
        // 3. 中斷所有worker線程
        interruptWorkers();
        // 4. 將隊列任務轉移到List中,同時刪除隊列中任務
        tasks = drainQueue();
    } finally {
        // 解鎖
        mainLock.unlock();
    }
    // 試圖終止線程池
    tryTerminate();
    return tasks;
}

內部方法調用

private void interruptWorkers() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        // 遍歷所有wokers集合,中斷所有正在執行任務的worker。
        for (Worker w : workers)
            w.interruptIfStarted();
    } finally {
        mainLock.unlock();
    }
}

// Worker
void interruptIfStarted() {
    Thread t;
    // worker已經運行,並且線程不爲空,沒有被中斷
    if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
        try {
            // 中斷線程
            t.interrupt();
        } catch (SecurityException ignore) {
        }
    }
}

awaitTermination(long timeout, TimeUnit unit) :

當前線程阻塞,直到某個事件首先發生。三個事件分別是使用shutdown命令後所有任務執行完畢、超時結束或者線程被中斷。

public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
    // 獲取超時時間,納秒數
    long nanos = unit.toNanos(timeout);
    // 加鎖
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        // 死循環
        for (;;) {
            // 如果線程池是TERMINATED,返回true
            if (runStateAtLeast(ctl.get(), TERMINATED))
                return true;
             // 超時時間爲0,返回false
            if (nanos <= 0)
                return false;
            // 一直阻塞,直到超時時間結束、中斷、被通知(signalled)
            nanos = termination.awaitNanos(nanos);
        }
    } finally {
        // 解鎖
        mainLock.unlock();
    }
}

tryTerminate()方法在將線程池狀態設爲TERMINATED後,會調 termination.signalAll();通知喚醒termination變量上等待的線程。

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