線程池之——ThreadPoolExecutor源碼深度解析

1.可以先看我上一篇,基礎解析https://blog.csdn.net/zhangkaixuan456/article/details/106840295

如類圖,其中 mainLock 是獨佔鎖,用來控制新增 Worker 線程時候的原子性,termination 是該鎖對應的條件隊列,在線程調用 awaitTermination 時候用來存放阻塞的線程。

Worker 繼承 AQS 和 Runnable 接口,是具體承載任務的對象,Worker 繼承了 AQS,自己實現了簡單不可重入獨佔鎖,其中 status=0 標示鎖未被獲取狀態,state=1 標示鎖已經被獲取的狀態,state=-1 是創建 Worker 時候默認的狀態,創建時候狀態設置爲 -1 是爲了避免在該線程在運行 runWorker() 方法前被中斷,下面會具體講解到。其中變量 firstTask 記錄該工作線程執行的第一個任務,thread 是具體執行任務的線程。

DefaultThreadFactory 是線程工廠,newThread 方法是對線程的一個修飾,其中 poolNumber 是個靜態的原子變量,用來統計線程工廠的個數,threadNumber 用來記錄每個線程工廠創建了多少線程,這兩個值也作爲線程池和線程的名稱的一部分。

 private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
       
        private static final long serialVersionUID = 6138294804551838833L;

        final Thread thread;
        /** Initial task to run.  Possibly null. */
        Runnable firstTask;
       
        volatile long completedTasks;

        Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }
    }

3.源碼分析

1 public void execute(Runnable command):execute 方法是提交任務 command 到線程池進行執行,用戶線程提交任務到線程池的模型圖如下所示:

如上圖可知 ThreadPoolExecutor 的實現實際是一個生產消費模型,其中當用戶添加任務到線程池時候相當於生產者生產元素,workers 線程工作集中的線程直接執行任務或者從任務隊列裏面獲取任務相當於消費者消費元素。用戶線程提交任務的 execute 方法具體代碼如下:

public void execute(Runnable command) {

    //(1) 如果任務爲null,則拋出NPE異常
    if (command == null)
        throw new NullPointerException();

    //(2)獲取當前線程池的狀態+線程個數變量的組合值
    int c = ctl.get();

    //(3)當前線程池線程個數是否小於corePoolSize,小於則開啓新線程運行
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }

    //(4)如果線程池處於RUNNING狀態,則添加任務到阻塞隊列
    if (isRunning(c) && workQueue.offer(command)) {

        //(4.1)二次檢查
        int recheck = ctl.get();
        //(4.2)如果當前線程池狀態不是RUNNING則從隊列刪除任務,並執行拒絕策略
        if (! isRunning(recheck) && remove(command))
            reject(command);

        //(4.3)否者如果當前線程池線程空,則添加一個線程
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    //(5)如果隊列滿了,則新增線程,新增失敗則執行拒絕策略
    else if (!addWorker(command, false))
        reject(command);
}

1.代碼(3)判斷如果當前線程池線程個數小於 corePoolSize,如上圖會在 workers 裏面新增一個核心線程(core 線程)執行該任務。

2.如果當前線程池線程個數大於等於 corePoolSize 執行代碼(4),如果當前線程池處於 RUNNING 狀態則添加當前任務到任務隊列,這裏需要判斷線程池狀態是因爲有可能線程池已經處於非 RUNNING 狀態,而非 RUNNING 狀態下是拋棄新任務的。

3.如果任務添加任務隊列成功,則代碼(4.2)對線程池狀態進行二次校驗,這是因爲添加任務到任務隊列後,執行代碼(4.2)前有可能線程池的狀態已經變化了,這裏進行二次校驗,如果當前線程池狀態不是 RUNNING 了則把任務從任務隊列移除,移除後執行拒絕策略;如果二次校驗通過,則執行代碼(4.3)重新判斷當前線程池裏面是否還有線程,如果沒有則新增一個線程。

4.如果代碼(4)添加任務失敗,則說明任務隊列滿了,則執行代碼(5)嘗試新開啓線程(如上圖 thread 3 和 thread 4)來執行該任務,如果當前線程池線程個數 > maximumPoolSize 則執行拒絕策略。

 

接下來看新增線程的 addWorkder 方法的源碼,如下:

private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

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

        //(7)循環cas增加線程個數
        for (;;) {
            int wc = workerCountOf(c);

            //(7.1)如果線程個數超限則返回false
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            //(7.2)cas增加線程個數,同時只有一個線程成功
            if (compareAndIncrementWorkerCount(c))
                break retry;
            //(7.3)cas失敗了,則看線程池狀態是否變化了,變化則跳到外層循環重試重新獲取線程池狀態,否者內層循環重新cas。
            c = ctl.get();  // Re-read ctl
            if (runStateOf(c) != rs)
                continue retry;
        }
    }

    //(8)到這裏說明cas成功了
    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
        //(8.1)創建worker
        final ReentrantLock mainLock = this.mainLock;
        w = new Worker(firstTask);
        final Thread t = w.thread;
        if (t != null) {

            //(8.2)加獨佔鎖,爲了workers同步,因爲可能多個線程調用了線程池的execute方法。
            mainLock.lock();
            try {

                //(8.3)重新檢查線程池狀態,爲了避免在獲取鎖前調用了shutdown接口
                int c = ctl.get();
                int rs = runStateOf(c);

                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                    //(8.4)添加任務
                    workers.add(w);
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            //(8.5)添加成功則啓動任務
            if (workerAdded) {
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

如上代碼主要分兩部分,第一部分的雙重循環目的是通過 cas 操作增加線程池線程數,第二部分主要是併發安全的把任務添加到 workers 裏面,並且啓動任務執行。

先看第一部分的代碼(6),如下所示:

s >= SHUTDOWN &&
                (rs != SHUTDOWN ||//(1)
              firstTask != null ||//(2)
              workQueue.isEmpty())//(3)

如上代碼,也就是說代碼(6)在下面幾種情況下會返回 false:

    1.當前線程池狀態爲 STOP,TIDYING,TERMINATED;

    2.當前線程池狀態爲 SHUTDOWN 並且已經有了第一個任務;

    3.當前線程池狀態爲 SHUTDOWN 並且任務隊列爲空。

回到上面看新增線程的 addWorkder 方法,發現內層循環作用是使用 cas 增加線程,代碼(7.1)如果線程個數超限則返回 false,否者執行代碼(7.2)執行 CAS 操作設置線程個數,cas 成功則退出雙循環,CAS 失敗則執行代碼(7.3)看當前線程池的狀態是否變化了,如果變了,則重新進入外層循環重新獲取線程池狀態,否者進入內層循環繼續進行 cas 嘗試。

執行到第二部分的代碼(8)說明使用 CAS 成功的增加了線程個數,但是現在任務還沒開始執行,這裏使用全局的獨佔鎖來控制把新增的 Worker 添加到工作集 workers。代碼(8.1)創建了一個工作線程 Worker。

代碼(8.2)獲取了獨佔鎖,代碼(8.3)重新檢查線程池狀態,這是爲了避免在獲取鎖前其他線程調用了 shutdown 關閉了線程池,如果線程池已經被關閉,則釋放鎖,新增線程失敗,否者執行代碼(8.4)添加工作線程到線程工作集,然後釋放鎖,代碼(8.5)如果判斷如果工作線程新增成功,則啓動工作線程。

 

3.2 工作線程 Worker 的執行

當用戶線程提交任務到線程池後,具體是使用 worker 來執行的,先看下 Worker 的構造函數:

Worker(Runnable firstTask) {
    setState(-1); // 在調用runWorker前禁止中斷
    this.firstTask = firstTask;
    this.thread = getThreadFactory().newThread(this);//創建一個線程
}

如上代碼構造函數內首先設置 Worker 的狀態爲 -1,是爲了避免當前 worker 在調用 runWorker 方法前被中斷(當其它線程調用了線程池的 shutdownNow 時候,如果 worker 狀態 >= 0 則會中斷該線程)。這裏設置了線程的狀態爲 -1,所以該線程就不會被中斷了。如下代碼運行 runWorker 的代碼(9)時候會調用 unlock 方法,該方法把 status 變爲了 0,所以這時候調用 shutdownNow 會中斷 worker 線程了。

 

接着我們再看看runWorker方法,代碼如下:

final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); //(9)status設置爲0,允許中斷
        boolean completedAbruptly = true;
        try {
           //(10)
            while (task != null || (task = getTask()) != null) {

                 //(10.1)
                w.lock();
               ...
                try {
                    //(10.2)任務執行前幹一些事情
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                        task.run();//(10.3)執行任務
                    } catch (RuntimeException x) {
                        thrown = x; throw x;
                    } catch (Error x) {
                        thrown = x; throw x;
                    } catch (Throwable x) {
                        thrown = x; throw new Error(x);
                    } finally {
                        //(10.4)任務執行完畢後幹一些事情
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
                    //(10.5)統計當前worker完成了多少個任務
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {

            //(11)執行清工作
            processWorkerExit(w, completedAbruptly);
        }
    }

如上代碼(10)如果當前 task==null 或者調用 getTask 從任務隊列獲取的任務返回 null,則跳轉到代碼(11)執行。如果 task 不爲 null 則執行代碼(10.1)獲取工作線程內部持有的獨佔鎖,然後執行擴展接口代碼(10.2)在具體任務執行前做一些事情,代碼(10.3)具體執行任務,代碼(10.4)在任務執行完畢後做一些事情,代碼(10.5)統計當前 worker 完成了多少個任務,並釋放鎖。

這裏在執行具體任務期間加鎖,是爲了避免任務運行期間,其他線程調用了 shutdown 或者 shutdownNow 命令關閉了線程池。

 

其中代碼(11)執行清理任務,其代碼如下:

private void processWorkerExit(Worker w, boolean completedAbruptly) {
     ...代碼太長,這裏就不展示了

    //(11.1)統計整個線程池完成的任務個數,並從工作集裏面刪除當前woker
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        completedTaskCount += w.completedTasks;
        workers.remove(w);
    } finally {
        mainLock.unlock();
    }

    //(11.2)嘗試設置線程池狀態爲TERMINATED,如果當前是shutdonw狀態並且工作隊列爲空
    //或者當前是stop狀態當前線程池裏面沒有活動線程
    tryTerminate();

    //(11.3)如果當前線程個數小於核心個數,則增加
    int c = ctl.get();
    if (runStateLessThan(c, STOP)) {
        if (!completedAbruptly) {
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            if (min == 0 && ! workQueue.isEmpty())
                min = 1;
            if (workerCountOf(c) >= min)
                return; // replacement not needed
        }
        addWorker(null, false);
    }
}

如上代碼(11.1)統計線程池完成任務個數,可知在統計前加了全局鎖,把當前工作線程中完成的任務累加到全局計數器,然後從工作集中刪除當前 worker。

代碼(11.2)判斷如果當前線程池狀態是 shutdonw 狀態並且工作隊列爲空或者當前是 stop 狀態當前線程池裏面沒有活動線程則設置線程池狀態爲 TERMINATED,如果設置爲了 TERMINATED 狀態還需要調用條件變量 termination 的 signalAll() 方法激活所有因爲調用線程池的 awaitTermination 方法而被阻塞的線程

代碼(11.3)則判斷當前線程裏面線程個數是否小於核心線程個數,如果是則新增一個線程。

 

3.3 shutdown 操作:調用 shutdown 後,線程池就不會在接受新的任務了,但是工作隊列裏面的任務還是要執行的,該方法立刻返回的,並不等待隊列任務完成在返回。代碼如下:

public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        //(12)權限檢查
        checkShutdownAccess();

        //(13)設置當前線程池狀態爲SHUTDOWN,如果已經是SHUTDOWN則直接返回
        advanceRunState(SHUTDOWN);

        //(14)設置中斷標誌
        interruptIdleWorkers();
        onShutdown(); 
    } finally {
        mainLock.unlock();
    }
    //(15)嘗試狀態變爲TERMINATED
    tryTerminate();
}

如上代碼(12)檢查如果設置了安全管理器,則看當前調用 shutdown 命令的線程是否有關閉線程的權限,如果有權限則還要看調用線程是否有中斷工作線程的權限,如果沒有權限則拋出 SecurityException 或者 NullPointerException 異常。

其中代碼(13)內容如下,如果當前狀態 >= SHUTDOWN 則直接返回,否者設置當前狀態爲 SHUTDOWN:

private void advanceRunState(int targetState) {
    for (;;) {
        int c = ctl.get();
        if (runStateAtLeast(c, targetState) ||
            ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))
            break;
    }
}

代碼(14)內容如下,設置所有空閒線程的中斷標誌,這裏首先加了全局鎖,同時只有一個線程可以調用 shutdown 設置中斷標誌,然後嘗試獲取 worker 自己的鎖,獲取成功則設置中斷標識,由於正在執行的任務已經獲取了鎖,所以正在執行的任務沒有被中斷。這裏中斷的是阻塞到 getTask() 方法,企圖從隊列裏面獲取任務的線程,也就是空閒線程。

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

代碼(15)判斷如果當前線程池狀態是 shutdonw 狀態並且工作隊列爲空或者當前是 stop 狀態當前線程池裏面沒有活動線程則設置線程池狀態爲 TERMINATED,如果設置爲了 TERMINATED 狀態還需要調用條件變量 termination 的 signalAll()方法激活所有因爲調用線程池的 awaitTermination 方法而被阻塞的線程

 

3.4 shutdownNow 操作

調用 shutdownNow 後,線程池就不會在接受新的任務了,並且丟棄工作隊列裏面裏面的任務,正在執行的任務會被中斷,該方法是立刻返回的,並不等待激活的任務執行完成在返回。返回值爲這時候隊列裏面被丟棄的任務列表。代碼如下:

public List<Runnable> shutdownNow() {


    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();//(16)權限檢查
        advanceRunState(STOP);//(17) 設置線程池狀態爲stop
        interruptWorkers();//(18)中斷所有線程
        tasks = drainQueue();//(19)移動隊列任務到tasks
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
    return tasks;
}

如上代碼首先調用代碼(16)檢查權限,然後調用代碼(17)設置當前線程池狀態爲 stop,然後執行代碼(18)中斷所有的工作線程,這裏需要注意的是中斷所有的線程,包含空閒線程和正在執行任務的線程,代碼如下:

private void interruptWorkers() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers)
                w.interruptIfStarted();
        } finally {
            mainLock.unlock();
        }
    }

3.4 awaitTermination 操作

當線程調用 awaitTermination 方法後,當前線程會被阻塞,知道線程池狀態變爲了 TERMINATED 才返回,或者等待時間超時才返回,整個過程獨佔鎖,代碼如下:

public boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException {
        long nanos = unit.toNanos(timeout);
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (;;) {
                if (runStateAtLeast(ctl.get(), TERMINATED))
                    return true;
                if (nanos <= 0)
                    return false;
                nanos = termination.awaitNanos(nanos);
            }
        } finally {
            mainLock.unlock();
        }
    }

如上代碼首先獲取了獨佔鎖,然後無限循環內部首先判斷當前線程池狀態是否至少是 TERMINATED 狀態,如果是則直接返回。否者說明當前線程池裏面還有線程在執行,則看設置的超時時間 nanos 是否小於 0,小於 0 則說明不需要等待,則直接返回;如果大於0則調用條件變量 termination 的 awaitNanos 方法等待 nanos 時間,期望在這段時間內線程池狀態內變爲 TERMINATED 狀態。

在講解 shutdown 方法時候提到當線程池狀態變爲 TERMINATED 後,會調用 termination.signalAll() 用來激活調用條件變量 termination 的 await 系列方法被阻塞的所有線程,所以如果在調用了 awaitTermination 之後調用了 shutdown 方法,並且 shutdown 內部設置線程池狀態爲 TERMINATED 了,則 termination.awaitNanos 方法會返回。

另外在工作線程 Worker 的 runWorker 方法內當工作線程運行結束後,會調用 processWorkerExit 方法,processWorkerExit 方法內部也會調用 tryTerminate 方法測試當前是否應該把線程池設置爲 TERMINATED 狀態,如果是,則也會調用 termination.signalAll() 用來激活調用線程池的 awaitTermination 方法而被阻塞的線程

另外當等待時間超時後,termination.awaitNanos 也會返回,這時候會重新檢查當前線程池狀態是否爲 TERMINATED,如果是則直接返回,否者繼續阻塞掛起自己。

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