ThreadPoolExecutor原始碼共讀

秋褲Boy發表於2019-01-21

一起共讀,共同閱步。

零:序言

原始碼閱讀本就是要貫注全神、戒驕戒躁的沉浸式過程。我本著浮躁至極的心態,單刀直入,從入口方法先殺入“敵軍”內部,讓大家短時間內享受到最大的學習成就感,然後再橫向鋪開,帶大家一窺原始碼的究竟。有不對之處,輕噴、指出。

java1.5引入的執行緒池的標準類,ThreadPoolExecutor。

我們通常是通過Executors這個工廠類來建立具體的例項,如:

Executors.newCachedThreadPool(...)
Executors.newScheduledThreadPool(...)
複製程式碼

前者建立的就是我們要講的ThreadPoolExecutor例項。後者是有延遲功能的執行緒池,ScheduledThreadPoolExecutor,有機會再講吧。ThreadPoolExecutor這個執行緒池例項,內部維護了一個執行緒的集合,用來存放執行緒;有一個存放待執行任務的佇列,在池內執行緒數達到最大值時,任務就暫時入隊,等待執行緒取走執行。所以,目前來看,ThreadPoolExecutor的結構如下:

ThreadPoolExecutor結構圖.png

零點一:ThreadPoolExecutor原始碼閱讀思維導圖

我會先列一下該原始碼涉及到的重要的邏輯方法,然後按使用時通常的呼叫順序,挨個講解,最後合併總結。

  • execute:執行任務方法,內部封裝了新建執行緒、任務入隊等重要邏輯
  • addWorker:新建執行緒方法
  • getTask:從任務佇列內獲取一個任務
  • runWorker:池內執行緒的主迴圈邏輯。提醒一下,多執行緒都會呼叫這同一個方法,所以尤其注意同步問題。

零點五:ThreadPoolExecutor內的關鍵變數解釋

  • workQueue[BlockingQueue],任務佇列,是一個BlockingQueue物件,執行緒安全
  • ctl[Integer],記錄了執行緒池的執行狀態值跟池內的執行緒數
  • workers[HashSet<Worker>],具體存放執行緒的set物件
  • corePoolSize[volatile int],執行緒池核心執行緒數配置,低於這個數值時,新進來的任務一律以新啟動執行緒處理

一:執行緒池狀態ctl基礎知識準備

ThreadPoolExecutor例項代表一個執行緒池,相應地,這個池子就有一些執行狀態,以及池子內執行緒數量的配置。這兩者的實現如下:

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 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;

// Packing and unpacking ctl
private static int runStateOf(int c)     { return c & ~CAPACITY; }
private static int workerCountOf(int c)  { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }
複製程式碼

執行緒池狀態跟池內執行緒數的統計都記錄在ctl這個AtomicInteger(它是誰,先自己查吧。後面估計會專門寫下,這裡只要記住它的加減是執行緒安全的就好)中。具體實現是: java內一個整型量是32位,這裡AtomicInteger也是。原始碼作者將ctl的32位中高3位用來記錄執行緒池狀態,低29位用來記錄執行緒數量。 驗證來看,COUNT_BITS的值是29,方法 runStateOf(int c) {return c & ~CAPACITY;} 這裡的c就是ctl變數,而CAPACITY就是一個mask面紗,用來從 ctl中提取上面兩個變數的,它是這樣的:

CAPACITY的點陣圖.png

所以,runState就是ctl取反後CAPACITY相與,也就是隻有高4位有效,正好對應執行緒池狀態的記錄位。 所以,各種狀態下,ctl的值如下: RUNNING:1001 x(28個),-1,最高位是符號位,這個<<位移操作是忽略符號位的位移 SHUTDOWN:0000 x(28), 0 STOP: 0001 x(28), 1 TIDYING: 0010 x(28), 2 TERMINATED: 0011 x(28), 3

二:入口函式execute(Runnable command)

用過執行緒池的人應該都用過這個入口函式,它就是用來將一個Runnable任務體放入執行緒池,下面讓我們來具體看看它的邏輯(程式碼塊沒法高亮了,大家看下程式碼段中註釋的翻譯部分):

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
         * 如果當前池內執行緒數小於corePoolSize,那就嘗試去新建一
         * start a new thread with the given command as its first
         * 條執行緒,傳進來的command引數作為該執行緒的第一個任務 
         * task.  The call to addWorker atomically checks runState and
         * 體。呼叫addWorker函式會自動檢查執行緒池的狀態和池內活躍的執行緒數
         * workerCount, and so prevents false alarms that would add
         * 如果在不該或不能新建執行緒時新建了,那不會丟擲異常,會返回false
         * threads when it shouldn't, by returning 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
         * 新建一個執行緒去執行任務體。(PS:因為ThreadPoolExecutor
         * stopped, or start a new thread if there are none.
         * 主要是通過addWorker來建立執行緒,所以,如果池內一個活躍執行緒都沒有,
         * 這時我們任務體入隊了,也沒有執行緒去跑...當然為什麼只檢查一遍?我是想,可
         * 能就只是作者單純地在這裡想檢查一遍,稍微確保下。因為即使這個二次檢查
         * 沒問題,後續的,到池內執行緒確切地去跑這個任務體之前的程式碼,每一行
         * 程式碼,都仍有發生這種情況的可能。這,就是多執行緒...)
         * 3. If we cannot queue task, then we try to add a new
         * 如果我們任務體入隊失敗,那我嘗試新建執行緒,如果還失敗
         * 那就說明執行緒池已經被shutdown了,或者整個池子已經滿了,那我們
         * 就去拒絕這個任務體。這個拒絕,就會用到所謂的RejectPolicy物件
         * thread.  If it fails, we know we are shut down or saturated
         * and so reject the task.
         */
        // 獲取ctl物件
        int c = ctl.get();
        // 如果池內活躍執行緒數小於corePoolSize
        if (workerCountOf(c) < corePoolSize) {
            // 新建執行緒,第二個引數true可以先忽略
            if (addWorker(command, true))
                return;
            // 新建執行緒失敗,那我們獲取最新的執行緒池狀態變數ctl
            c = ctl.get();
        }
        // 如果當前執行緒池仍在執行而且任務體入隊成功。
        // (workQueue就是ThreadPoolExecutor具體的任務佇列。
        // 而這裡就是我們上面註釋提到的那段二次檢查的邏輯)
        if (isRunning(c) && workQueue.offer(command)) {
            // 二次檢查。獲取最新的執行緒池狀態欄位
            int recheck = ctl.get();
            // 如果執行緒不在執行狀態 並且也成功把入隊的任務體刪除了
            // 那就菜哦用拒絕策略來拒絕
            if (! isRunning(recheck) && remove(command))
                reject(command);
            // 或者,線上程池內活躍的執行緒數為0時,新建一個執行緒
            // 這裡傳參跟上面不一樣,先忽略。記錄這個新啟動一個執行緒就夠了
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        // 如果上面的If失敗了,就嘗試新啟動執行緒,啟動失敗了,那說明
        // 上面的失敗,是isRunning造成的,所以拒絕任務體。啟動成功了,那就是成功了。
        else if (!addWorker(command, false))
            reject(command);
    }
複製程式碼

這裡涉及到ThreadPoolExecutor執行緒池增加執行緒的一個判斷邏輯: 每當ThreadPoolExecutor.execute執行一個任務時,先判斷corePoolSize,當池內執行緒數小於這個時,直接新增執行緒,若大於這個,則向workQueue任務佇列入隊,佇列滿了時,則以maximumPoolSize為界開始繼續新建執行緒,當超過maximumPoolSize時,就採用最後的RejectPolicy進行拒絕處理。

三: 函式addWorker(Runnable firstTask, boolean core)

這個函式主要邏輯是新啟動一個執行緒,firstTask是新啟動執行緒的第一個任務,可以為null,為null時,就是單純地啟動一個執行緒,記得我們之前在execute(Runnable command)方法中,線上程池內沒有有效執行緒時,呼叫firstTasknull的方法來啟動一條執行緒。 第二個引數core是用來辨別,啟動一個新執行緒時,是以corePoolSize這個執行緒數配置量來作為限制,還是以maximumPoolSize這個執行緒數配置量作為限制。 看下原始碼(邏輯主要放註釋裡了):

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

            // Check if queue empty only if necessary.
            // 檢查任務佇列在它必要的時候才能為空。這裡
            // 只能直接翻譯下,具體的,if裡的判斷邏輯也能推斷出來
            // 但是目前我也不能確定說出在這種情況下要false退出的
            // 原因。如果想搞清它,可能只能徹底把執行緒池的執行狀態、
            // 執行緒池內的執行緒數、任務佇列內的任務數三者所有可能的情況的
            // 前提下才能確定。這裡待大神指出來了。
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

            for (;;) {
                int wc = workerCountOf(c);
                // 第二個引數的作用就在這裡產生了!
                // 這裡在確保池內執行緒數不超過ctl極限CAPACITY
                // 以及不超過相應的xxxPoolSize的情況下,通過
                // CAI操作去給執行緒數加1,成功了,則跳出retry標記後
                // 的迴圈。至於CAI是什麼?先記住它是執行緒安全的給數值+1的操作就好
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                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
            }
        }
        // 到此位置,執行緒池內的執行緒數標記欄位已經加1了
        // 接下來的,就是具體新增一個執行緒的操作了
        // 
        // 這裡就不可避免的涉及到了ThreadPoolSize中的
        // Worker這個內部類了,這個類就是具體的ThreadPoolSize
        // 內部用來代表一個執行緒的封裝物件,他封裝了一個執行緒例項
        // ,用來跑具體的任務;封裝了一個Runnable 例項,代表具體的任務;
        // 同時,它繼承、實現了AQS(AbstractQueuedSynchronizer)跟Runnable,所以,這個
        // Worker例項可以理解成一個小勞工,有自己的執行執行緒,有
        // 自己的具體的執行任務體,同時,自己也有同步機制AQS。
        // 這裡涉及到AQS,大傢伙就暫且理解成AQS賦予Worker同步的性質即可(呼叫AQS的方法就能實現)
        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            // 初始化一個Worker勞工,同時指定給他的任務。這個任務
            // 可以為null,空。表示什麼也不做。同時,初始化的時候,也會
           // 初始化Worker體內的執行緒物件,這條執行緒的物件的啟動,是
           // 在worker物件的Runnable.run實現方法裡
            w = new Worker(firstTask);
            final Thread t = w.thread;
                // 這個mainLock是ThreadPoolExecutor用來同步對
                // workers執行緒佇列的CRUD操作的
                mainLock.lock();
                try {
                    // Recheck while holding lock.
                    // Back out on ThreadFactory failure or if
                    // shut down before lock acquired.
                    int rs = runStateOf(ctl.get());
                    // 當執行緒池處於RUNNING狀態,則可以繼續操作;
                    // 或者當執行緒池處於SHUTDOWN,但是firstTask 為null
                    // 也就是說,這裡是為了增加一個執行緒的,所以,也可以放行
                    // 因為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 {
            // 如果執行緒啟動失敗,則執行回滾操作
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }
複製程式碼

四: Worker物件的執行緒start()

這裡講述的方法是接上面addWorker時,成功呼叫的t.start(),這裡啟動了Worker封裝的執行緒。這個執行緒是Worker建構函式裡生成的,如下:

        /**
         * Creates with given first task and thread from ThreadFactory.
         * @param firstTask the first task (null if none)
         */
        Worker(Runnable firstTask) {
            // 這裡設定了AQS的state,用來抑制interrupt直到
            // runWorker方法
            setState(-1); // inhibit interrupts until runWorker
            // 這裡傳遞了執行緒的任務體,可以為null
            this.firstTask = firstTask;
            // 初始化執行緒時,給執行緒指定了worker例項自身這個Runnable,因此,執行緒在start後,
            // 就是在執行worker當前例項自身的run方法
            this.thread = getThreadFactory().newThread(this);
        }
複製程式碼

看完上面程式碼的註釋,接著看worker例項自身的run方法

        /** Delegates main run loop to outer runWorker  */
        public void run() {
            runWorker(this);
        }
複製程式碼

可以看到這裡呼叫了runWorker方法,傳參是worker自身。runWorker是同一個ThreadPoolExecutor例項的方法,所以,執行緒池例項下的所有Worker執行緒都是在跑這同一個runWorker方法。

五: 執行緒池的迴圈體方法runWorker(Worker worker)

    /**
     * Main worker run loop.  Repeatedly gets tasks from queue and
     * executes them...
     */
final void runWorker(Worker w) {
        // 這裡獲取到執行緒物件,其實就是引數Worker物件內封裝的Thread物件。
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        // allow interrupts,允許Interrupt打斷,記得Worker物件的建構函式嘛?
        // 建構函式一開始就呼叫了setState(-1)去抑制interrupt。這裡就是去釋放它。
        // 當然,這裡具體的抑制interrupt的含義,要結合AQS來了解了,我後面再加吧。
        w.unlock();
        boolean completedAbruptly = true;
        try {
            // 如果Worker中的firstTask物件不是空的,則
            // 直接跑它;若不然,呼叫getTask從佇列中獲取一條任務來執行。這裡
            // 會一直while迴圈,所以worker們在任務佇列中有任務時
            // 會一直在這個runWorker中迴圈while取任務執行
            while (task != null || (task = getTask()) != null) {
                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
                // 這裡有一個很巧妙的判斷邏輯,這段程式碼也主要是實現了
                // 上面英文註釋中的邏輯,但是可能比較難理解,稍等
                // 在後面我會專門講一下
                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();
                }
            }
            // 這個引數記錄worker執行是否是被打斷的,如果不是,程式碼
            // 會安全地走到這裡,然後置欄位為false。
            // 否則,異常情況下就直接跳到finally中了,值仍為初始化時的true
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }
複製程式碼

這段原始碼上方我放了一段註釋,翻譯過來就是: worker物件的主要Loop迴圈體。從佇列(workQueue)中獲取任務體,然後執行。

六: 從任務佇列獲取任務的getTask函式

private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?
        // 這個for(;;)也是個無限迴圈的實現,它比while(true)的好處是
        // 在位元組碼層面上,它比while(true)少兩行位元組碼程式碼,所以
        // 效率更高
        for (;;) {
            // 獲取執行緒池最新狀態
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }

            int wc = workerCountOf(c);

            // Are workers subject to culling?
            // allowCoreThreadTimeOut表示coreThread,其實是判斷
            // 執行緒數在這個coreThreadPoolSize範圍內時,執行緒是否可以超時。
            // 這裡的判斷邏輯也很巧妙,如果allowxxxTimeOut為true,coreThread
            // 可以超時,則 || 後面判斷coreThread的邏輯也就無所謂了,是吧。
            // 但如果allowxxxxTimeOut為false,coreThread不允許超時,
            // 則需要去判斷在判斷的執行緒是否實在coreThread範圍內,是的話,
            // 則最終結果也為false,符合coreThread不能超時的邏輯;如果大於,
            // 則說明當前方法的執行緒不是在coreThread,
            // 注意去理解這個是不是coreThread這個概念
            // 所以,timed為true,也就是可以超時,符合邏輯
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
            // 這裡我把程式碼格式化了下,方便大家去看
            // 這裡的判斷就是,在符合了一些邏輯後,就去直接
            // wokerCount減一,代表當前這個woker就直接幹掉了,
            // 而在方法內返回null這個邏輯,在呼叫getTask的程式碼處
            // 確實也是去幹掉當前的worker例項。但是,woker不能
            // 瞎幹掉,必須要確保執行緒池能正常產生作用,這個正常作用
            // 的實現,要麼就是幹掉當前的worker還剩下至少一個,
            // 要麼就是任務佇列空了,這個邏輯就在(wc > 1 || workQueue.isEmpty)
            // 實現了。再來看 && 之前,在當前執行緒數大於
            // maximumPoolSize限制時,或者當前woker可以超時,
            // 即timed為true,同時,上一次獲取任務體時也超時了(timedOut)
            // 則,當前的worker就幹掉。這段邏輯有一個timedOut
            // 判斷,即上一次當前worker獲取任務體時就超時了。
            // 我猜測,加這個邏輯,可能就是純粹的統計學上的效率
            // 提高。當然,歡迎更多想法。
            //
            // 在符合上述條件後,CAS操作來減少workerCount數
            // 再返回null,去幹掉當前worker例項。
            if (
                (wc > maximumPoolSize || (timed && timedOut))
                &&
                 (wc > 1 || workQueue.isEmpty())
                ) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
                // 根據當前的worker是否可以超時,呼叫BlockingQueue
                // 的不同方法來獲取任務體。
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                // 獲取到任務體,則返回
                if (r != null)
                    return r;
                // 超時了,記錄標記位
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }
複製程式碼

七: 截止目前,所有的任務入隊、執行緒迴圈、取任務執行任務的邏輯就已經都看完了

這裡我們總結下:

ThreadPoolExecutor的實際邏輯圖

ThreadPoolExecutor結構圖.png

workers執行緒集合中的Worker物件,在runWorker中迴圈自workQueue中獲取Runnable任務體進行執行。對workers執行緒集合的訪問要經過mainLock這個鎖。

shutdown等執行緒池結束等方法,後續會講。程式碼講解中涉及一些跟執行緒同步相關的細碎的小邏輯,建議在理解了主要邏輯後,去重點理解,這些點的思想是很有價值的思想。

相關文章