(轉)線程池類ThreadPoolExecutor源碼解讀

原文鏈接:https://blog.csdn.net/xiaojin21cen/article/details/87359148#51_submit_265

目錄

一、任務的提交過程

1.1 submit方法源碼

1.2 execute方法源碼

1.3、addWorker源碼

2、線程的執行過程

2.1、runWorker源碼

2.2、getTask 源代碼

3、 線程池的關閉過程

3.1、shutdown源碼

3.2、interruptIdleWorkers 源碼


一、任務的提交過程

1.1 submit方法源碼

public Future<?> submit(Runnable task) {
    if (task == null) 
		throw new NullPointerException();

    RunnableFuture<Void> ftask = newTaskFor(task, null);
    execute(ftask);
    return ftask;
}
 
public <T> Future<T> submit(Callable<T> task) {
    if (task == null)
		throw new NullPointerException();

    RunnableFuture<T> ftask = newTaskFor(task);
    execute(ftask);
    return ftask;
}

submit 的實現方法位於抽象類 AbstractExecutorService 中,而此時 execute 方法還未實現(而是在 AbstractExecutorService 的繼承類 ThreadPoolExecutor 中實現)。submit 有三種重載方法,這裏我選取了兩個常用的進行分析,可以看出無論哪個submit 方法都最終調用了 execute 方法。

1.2 execute方法源碼

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    /*
     * clt記錄着runState和workerCount
     */
    int c = ctl.get();
    /*
     * workerCountOf方法取出低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添加到workQueue中了,
        // 這時需要移除該command
        // 執行過後通過handler使用拒絕策略對該任務進行處理,整個方法返回
        if (! isRunning(recheck) && remove(command))
            reject(command);
        /*
         * 獲取線程池中的有效線程數,如果數量是0,則執行addWorker方法
         * 這裏傳入的參數表示:
         * 1. 第一個參數爲null,表示在線程池中創建一個線程,但不去啓動;
         * 2. 第二個參數爲false,將線程池的有限線程數量的上限設置爲maximumPoolSize,添加線程時根據maximumPoolSize來判斷;
         * 如果判斷workerCount大於0,則直接返回,在workQueue中新增的command會在將來的某個時刻被執行。
         */
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    /*
     * 如果執行到這裏,有兩種情況:
     * 1. 線程池已經不是RUNNING狀態;
     * 2. 線程池是RUNNING狀態,但workerCount >= corePoolSize並且workQueue已滿。
     * 這時,再次調用addWorker方法,但第二個參數傳入爲false,將線程池的有限線程數量的上限設置爲maximumPoolSize;
     * 如果失敗則拒絕該任務
     */
    else if (!addWorker(command, false))
        reject(command);
}

由於execute方法中多次調用 addWorker ,我們這裏就簡要介紹一下它,這個方法的主要作用就是創建一個線程來執行Runnnable對象。

addWorker(Runnable firstTask, boolean core)

第一個參數 firstTask 不爲null,則創建的線程就會先執行 firstTask對象,然後去阻塞隊列中取任務,否直接到阻塞隊列中獲取任務來執行。
第二個參數,core 參數爲真,則用 corePoolSize 作爲池中線程數量的最大值;
爲假,則以maximumPoolSize 作爲池中線程數量的最大值。

簡要分析一下execute源碼,執行一個Runnable對象時,首先通過workerCountOf(c) 獲取線程池中線程的數量,如果池中的數量小於 corePoolSize 就調用 addWorker 添加一個線程來執行這個任務。否則通過 workQueue.offer(command) 方法入列。如果入列成功還需要在一次判斷池中的線程數,因爲我們創建線程池時可能要求核心線程數量爲0,所以我們必須使用 addWorker(null, false) 來創建一個臨時線程去阻塞隊列中獲取任務來執行。

isRunning( c ) 的作用是判斷線程池是否處於運行狀態,如果入列後發現線程池已經關閉,則出列。不需要在入列前判斷線程池的狀態,因爲判斷一個線程池工作處於RUNNING狀態到執行入列操作這段時間,線程池可能被其它線程關閉了,所以提前判斷毫無意義。

1.3、addWorker源碼

下面代碼中用到的Worker類只是對Runnable的一個封裝,內部引用了Runnable和Thread,源碼解析可以參看這篇blog

 線程池worker介紹 

private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    for (;;) {
        int c = ctl.get();
        // 獲取運行狀態
        int rs = runStateOf(c);
         
        /*
         * 這個if判斷
         * 如果rs >= SHUTDOWN,則表示此時不再接收新任務;
         * 接着判斷以下3個條件,只要有1個不滿足,則返回false:
         * 1. rs == SHUTDOWN,這時表示關閉狀態,不再接受新提交的任務,但卻可以繼續處理阻塞隊列中已保存的任務
         * 2. firsTask爲空
         * 3. 阻塞隊列不爲空
         *
         * 首先考慮rs == SHUTDOWN的情況
         * 這種情況下不會接受新提交的任務,所以在firstTask不爲空的時候會返回false;
         * 然後,如果firstTask爲空,並且workQueue也爲空,則返回false,
         * 因爲隊列中已經沒有任務了,不需要再添加線程了
         */
        // Check if queue empty only if necessary.
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;
        for (;;) {
            // 獲取線程數
            int wc = workerCountOf(c);
            // 如果wc超過CAPACITY,也就是ctl的低29位的最大值(二進制是29個1),返回false;
            // 這裏的core是addWorker方法的第二個參數,如果爲true表示根據corePoolSize來比較,
            // 如果爲false則根據maximumPoolSize來比較。
            //
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            // 嘗試增加workerCount,如果成功,則跳出第一個for循環
            if (compareAndIncrementWorkerCount(c))
                break retry;
            // 如果增加workerCount失敗,則重新獲取ctl的值
            c = ctl.get();  // Re-read ctl
            // 如果當前的運行狀態不等於rs,說明狀態已被改變,返回第一個for循環繼續執行
            if (runStateOf(c) != rs)
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
        }
    }
    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 {
                // Recheck while holding lock.
                // Back out on ThreadFactory failure or if
                // shut down before lock acquired.
                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()) // precheck that t is startable
                        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;
}

2、線程的執行過程

2.1、runWorker源碼

final void runWorker(Worker w) {

    Thread wt = Thread.currentThread();

    // 獲取第一個任務

    Runnable task = w.firstTask;

    w.firstTask = null;

    // 允許中斷

    w.unlock(); // allow interrupts

    // 是否因爲異常退出循環

    boolean completedAbruptly = true;

    try {

        // 如果task爲空,則通過getTask來獲取任務

        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();

            }

        }

        completedAbruptly = false;

    } finally {

        processWorkerExit(w, completedAbruptly);

    }
}

這裏說明一下第一個if判斷,目的是:

  • 如果線程池正在停止,那麼要保證當前線程是中斷狀態;
  • 如果不是的話,則要保證當前線程不是中斷狀態;

總結一下runWorker方法的執行過程:

  1. while循環不斷地通過getTask()方法獲取任務;
  2. getTask()方法從阻塞隊列中取任務;
  3. 如果線程池正在停止,那麼要保證當前線程是中斷狀態,否則要保證當前線程不是中斷狀態;
  4. 調用task.run()執行任務;
  5. 如果task爲null則跳出循環,執行processWorkerExit()方法;
  6. runWorker方法執行完畢,也代表着Worker中的run方法執行完畢,銷燬線程。

這裏的beforeExecute方法和afterExecute方法在ThreadPoolExecutor類中是空的,留給子類來實現。

completedAbruptly 變量來表示在執行任務過程中是否出現了異常,在processWorkerExit 方法中會對該變量的值進行判斷。


Thread的run方法實際上調用了 Worker 類的 runWorker 方法,而 Worker 類繼承了 AQS 類,並實現了 lockunlock 、trylock 方法。但是這些方法不是真正意義上的鎖,所以在代碼中加鎖操作和解鎖操作沒有成對出現。

runWorker 方法中獲取到任務就“加鎖”,完成任務後就“解鎖”。也就是說在“加鎖”到“解鎖”的這段時間內,線程處於忙碌狀態,而其它時間段,處於空閒狀態。線程池就可以通過 trylock 方法來確定這個線程是否空閒。

getTask 方法的主要作用是從阻塞隊列中獲取任務。

beforeExecute(wt, task) 和 afterExecute(task, thrown) 是個鉤子函數,如果我們需要在任務執行之前和任務執行以後進行一些操作,那麼我們可以自定義一個繼承 ThreadPoolExecutor 類,並覆蓋這兩個方法。

2.2、getTask 源代碼

private Runnable getTask() {
    // timeOut變量的值表示上次從阻塞隊列中取任務時是否超時
    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.
        /*
         * 如果線程池狀態rs >= SHUTDOWN,也就是非RUNNING狀態,再進行以下判斷:
         * 1. rs >= STOP,線程池是否正在stop;
         * 2. 阻塞隊列是否爲空。
         * 如果以上條件滿足,則將workerCount減1並返回null。
         * 因爲如果當前線程池狀態的值是SHUTDOWN或以上時,不允許再向阻塞隊列中添加任務。
         */
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }
        int wc = workerCountOf(c);
        // Are workers subject to culling?
        // 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;
        }
    }
}

可以看出如果允許線程在keepAliveTime時間內未獲取到任務線程就銷燬就調用workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS),否則會調用workQueue.take()方法(該方法即使獲取不到任務就會一直阻塞下去)。而確定是否使用workQueue.poll方法只有兩個條件決定,一個是當前池中的線程是否大於核心線程數量,第二個是是否允許核心線程銷燬,兩者其一滿足就會調用該方法。

3、 線程池的關閉過程

3.1、shutdown源碼

public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        advanceRunState(SHUTDOWN);
        interruptIdleWorkers();
        onShutdown();// hook for ScheduledThreadPoolExecutor
    }finally {
        mainLock.unlock();
    }
    tryTerminate();
}

advanceRunState(SHUTDOWN) 的作用是通過CAS操作將線程池的狀態更改爲 SHUTDOWN 狀態。
interruptIdleWorkers 是對空閒的線程進行中斷,它實際上調用了重載帶參數的函數 interruptIdleWorkers(false)

onShutdown 也是一個鉤子函數

3.2、interruptIdleWorkers 源碼

private void interruptIdleWorkers(boolean onlyOne) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        for (Worker w : workers) {
            Thread t = w.thread;
            if (!t.isInterrupted() && w.tryLock()) {
                try {
                    t.interrupt();
                }catch (SecurityException ignore) {
                }finally {
                    w.unlock();
                }
            }
            if (onlyOne)
                break;
        }
    }finally {
        mainLock.unlock();
    }
}

通過workers容器,遍歷池中的線程,對每個線程進行tryLock()操作,如果成功說明線程空閒,則設置其中斷標誌位。而線程是否響應中斷則由任務的編寫者決定。

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