透過ThreadPoolExecutor學習一下代碼回滾

1 前言

提起回滾,我們首先的能想到是事務回滾。這個詞對於一個有一年以上開發經驗不陌生。事務是一組組合成邏輯工作單元的操作,雖然系統中可能會出錯,但事務將控制和維護事務中每個操作的一致性和完整性。而對於目前SpringBoot盛行的當下,給一個service類添加事務也是輕而易舉的事。然而對於代碼層面的回滾,我們的回滾意識就很薄弱。那麼今天我們就通過JDK提供的併發包中的ThreadPoolExecutor看一下JDK框架中是怎麼處理事物的。

2 代碼片段

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

        // Check if queue empty only if necessary.
        // 初始的ctl爲RUNNING(-1),小於SHUTDOWN(0)
        // 狀態大於等於SHUTDOWN,說明已經執行了shutdown()或者shutdownNow()方法,此時線程池進入結束狀態

        if (rs >= SHUTDOWN &&
                /*
                 *    狀態爲shutdown & 傳入的任務爲空 & 列隊不爲空的否命題,也就是這三個條件至少有一個不滿足
                 * 則返回false,也就是加入列隊失敗。挨個看一下每個爲false是什麼情況:
                 * 1.rs != SHUTDOWN ==> 由於上面判斷rs>=SHUTDOWN滿足,所以這裏是 rs>SHUTDOWN,也就是
                 *   對於狀態STOP/TIDYING/TERMINATED不能將任務加入列隊中。
                 * 2.要執行第二個判斷前提是rs == SHUTDOWN.當firstTask != null,可以直接判斷返回false.
                 * 3.工作列隊爲空,也直接返回false.
                 */
            ! (rs == SHUTDOWN &&
                    // 第一個任務爲null
               firstTask == null &&
                    // worker隊列不爲空
               ! workQueue.isEmpty()))
            // 返回
            return false;

        /*
         * 等走到這一步,包含幾種情況:
         * 1.當前狀態處於RUNNING
         * 2.當前狀態處於SHUTDOWN,firstTask== null 列隊不爲空向下執行
         */
        for (;;) {
            // worker數量
            int wc = workerCountOf(c);
            // worker數量大於等於最大容量
            if (wc >= CAPACITY ||
                    // worker數量大於等於核心線程池大小或者最大線程池大小
                    //這裏決定是用核心線程大小比較還是最大的比較
                    //如果大於執行的線程數,加入工作列隊失敗。
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            // 比較並增加worker的數量
            //當前工作線程數小於核心線程或者最大線程數量,通過CAS操作增加worker數量
            if (compareAndIncrementWorkerCount(c))
                // 跳出外層循環
                break retry;
            // 獲取線程池控制狀態
            c = ctl.get();  // Re-read ctl
            // 此次的狀態與上次獲取的狀態不相同
            if (runStateOf(c) != rs)
                // 跳過本循環體,繼續上層循環
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
        }
    }
    // worker開始標識
    //主要用於判斷線程是否運行成功,如果不成功,需要將其移除列隊,並且將ctl-1。
    boolean workerStarted = false;
    // worker被添加標識
    //這個標識主要用於判斷work是否填入列隊成功,如果成功則運行
    boolean workerAdded = false;

    Worker w = null;
    try {
        // 初始化worker
        w = new Worker(firstTask);
        // 獲取worker對應的線程
        final Thread t = w.thread;
        // 線程不爲null
        if (t != null) {
            // 線程池鎖
            final ReentrantLock mainLock = this.mainLock;
            // 獲取鎖
            mainLock.lock();
            try {
                // Recheck while holding lock.
                // Back out on ThreadFactory failure or if
                // shut down before lock acquired.
                // 線程池的運行狀態
                int rs = runStateOf(ctl.get());
                // 小於SHUTDOWN
                if (rs < SHUTDOWN ||
                        // 等於SHUTDOWN並且firstTask爲null
                    (rs == SHUTDOWN && firstTask == null)) {
                    // 線程剛添加進來,還未啓動就存活
                    if (t.isAlive()) // precheck that t is startable
                        // 拋出線程狀態異常
                        throw new IllegalThreadStateException();
                    // 將worker添加到worker集合
                    workers.add(w);
                    // 獲取worker集合的大小
                    int s = workers.size();
                    // 隊列大小大於largestPoolSize
                    if (s > largestPoolSize) {
                        // 重新設置largestPoolSize
                        largestPoolSize = s;
                    }
                    // 設置worker已被添加標識
                    workerAdded = true;
                }
            } finally {
                // 釋放鎖
                mainLock.unlock();
            }
            // worker被添加
            if (workerAdded) {
                // 開始執行worker的run方法
                t.start();
                // 設置worker已開始標識
                workerStarted = true;
            }
        }
    } finally {
        // worker沒有開始
        if (! workerStarted)
            // 添加worker失敗
            addWorkerFailed(w);
    }
    return workerStarted;
}

這段代碼主要用於將線程添加到線程池中,通讀之後,我們發現這60多行(不包括註釋)代碼主要乾了三件事。

1) workCount變量+1,見代碼[48行] if (compareAndIncrementWorkerCount(c))

2)將線程封裝成Worker對象,加入workers變量中,見代碼 [94行] workers.add(w);

3)啓動線程。見代碼[112行]:t.start();

如果讓我們寫,我想也就是三四行代碼(最多再加個拋出異常什麼的)就能解決掉,但是JDK框架卻寫了將近60行代碼來保證代碼的健壯性及安全性。

我們今天主要是看回滾,所以其他代碼我們就不去深究。

2. 涉及到回滾的變量

boolean workerStarted = false;
boolean workerAdded = false;

workerStarted:主要記錄線程是否啓動成功。

workderAdded:主要記錄work是否添加到works變量中。

2.1 workerAdded設置

workerAdded設置是在代碼的94-103行,也就是將先前線程封裝的work成功放到works集合中的之後,修改此變量。當然如果沒有添加成功,這裏的標識還是不能被修改的。

2.2  workerStarted設置

workerAdded設置是在代碼的110-114行。這裏運行線程有個前提,就是將將先前線程封裝的work成功放到works集合中的之後,也就是workerAdded爲true的情況下。當線程運行成功,workerStarted會被設置成true。

2.3 代碼回滾

在這段代碼中我們發現有兩處處出現try語句,然而這兩處並沒有catch語句,而是在finnaly代碼塊自行處理了這些異常。處理的方式就是代碼回滾。

這裏有一點設計巧妙的地方是,如果【workers.add(w);】執行成功,但是workerAdded變量沒有設置成功,首先代碼110-114行不會被執行,workerStarted仍然是false,最終在119-122行執行代碼回滾。如果workerAdded設置成功,線程啓動失敗,即workerStarted = true;不會設置成功,最終finally也會進行代碼回滾。只有workerAdded和workerStarted都設置成功,纔不會進行代碼回滾。

2.4 代碼回滾的邏輯

當需要執行代碼回滾,必須先明確這個方法幹了那些事,上面提到總共做了三件事。那麼我們看看代碼回滾的邏輯實施對這三件事進行回滾。

private void addWorkerFailed(Worker w) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        if (w != null)
            workers.remove(w);
        decrementWorkerCount();
        tryTerminate();
    } finally {
        mainLock.unlock();
    }
}

通過上面代碼也看執行了works移除當前線程,workerCount-1的操縱。

3 回滾的場景

對於單個方法,如果我們在方法體中修改了成員變量,比如上面代碼中的workCount和ctl變量的情況下,如果在又任何異常拋出,我們根據業務場景將其設置還原。

最後還有一個小問題,就是上述代碼中在設置ctl過程中並沒有上鎖,而在works的add中卻上鎖了,大家可以想想其中的原因。

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