Java併發包中執行緒池ThreadPoolExecutor原理探究

風沙迷了眼發表於2019-06-13

一、執行緒池簡介

  執行緒池的使用主要是解決兩個問題:①當執行大量非同步任務的時候執行緒池能夠提供更好的效能,在不使用執行緒池時候,每當需要執行非同步任務的時候直接new一個執行緒來執行的話,執行緒的建立和銷燬都是需要開銷的。而執行緒池中的執行緒是可複用的,不需要每次執行非同步任務的時候重新建立和銷燬執行緒;②執行緒池提供一種資源限制和管理的手段,比如可以限制執行緒的個數,動態的新增執行緒等等。

  在下面的分析中,我們可以看到,執行緒池使用一個Integer的原子型別變數來記錄執行緒池狀態和執行緒池中的執行緒數量,通過執行緒池狀態來控制任務的執行,每個工作執行緒Worker執行緒可以處理多個任務。

二、ThreadPoolExecutor類

1、我們先簡單看一下關於ThreadPoolExecutor的一些成員變數以及其所表示的含義

   ThreadPoolExecutor繼承了AbstractExecutorService,其中的成員變數ctl是一個Integer型別的原子變數,用來記錄執行緒池的狀態和執行緒池中的執行緒的個數,類似於前面講到的讀寫鎖中使用一個變數儲存兩種資訊。這裡(Integer看做32位)ctl高三位表示執行緒池的狀態,後面的29位表示執行緒池中的執行緒個數。如下所示是ThreadPoolExecutor原始碼中的成員變數

 1 //(高3位)表示執行緒池狀態,(低29位)表示執行緒池中執行緒的個數;
 2 // 預設狀態是RUNNING,執行緒池中執行緒個數為0
 3 private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
 4 
 5 //表示具體平臺下Integer的二進位制位數-3後的剩餘位數表示的數才是執行緒的個數;
 6 //其中Integer.SIZE=32,-3之後的低29位表示的就是執行緒的個數了
 7 private static final int COUNT_BITS = Integer.SIZE - 3;
 8 
 9 //執行緒最大個數(低29位)00011111111111111111111111111111(1<<29-1)
10 private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
11 
12 //執行緒池狀態(高3位表示執行緒池狀態)
13 //111 00000000000000000000000000000
14 private static final int RUNNING    = -1 << COUNT_BITS;
15 
16 //000 00000000000000000000000000000
17 private static final int SHUTDOWN   =  0 << COUNT_BITS;
18 
19 //001 00000000000000000000000000000
20 private static final int STOP       =  1 << COUNT_BITS;
21 
22 //010 00000000000000000000000000000
23 private static final int TIDYING    =  2 << COUNT_BITS;
24 
25 //011 00000000000000000000000000000
26 private static final int TERMINATED =  3 << COUNT_BITS;
27 
28 //獲取高3位(執行狀態)==> c & 11100000000000000000000000000000
29 private static int runStateOf(int c)     { return c & ~CAPACITY; }
30 
31 //獲取低29位(執行緒個數)==> c &  00011111111111111111111111111111
32 private static int workerCountOf(int c)  { return c & CAPACITY; }
33 
34 //計算原子變數ctl新值(執行狀態和執行緒個數)
35 private static int ctlOf(int rs, int wc) { return rs | wc; }

  下面我們簡單解釋一下上面的執行緒狀態的含義:

  ①RUNNING:接受新任務並處理阻塞佇列中的任務

  ②SHUTDOWN:拒絕新任務但是處理阻塞佇列中的任務

  ③STOP:拒絕新任務並拋棄阻塞佇列中的任務,同時會中斷當前正在執行的任務

  ④TIDYING:所有任務執行完之後(包含阻塞佇列中的任務)當前執行緒池中活躍的執行緒數量為0,將要呼叫terminated方法

  ⑥TERMINATED:終止狀態。terminated方法呼叫之後的狀態

2、下面初步瞭解一下ThreadPoolExecutor的引數以及實現原理

  ①corePoolSize:執行緒池核心現車個數

  ②workQueue:用於儲存等待任務執行的任務的阻塞佇列(比如基於陣列的有界阻塞佇列ArrayBlockingQueue、基於連結串列的無界阻塞佇列LinkedBlockingQueue等等)

  ③maximumPoolSize:執行緒池最大執行緒數量

  ④ThreadFactory:建立執行緒的工廠

  ⑤RejectedExecutionHandler:拒絕策略,表示當佇列已滿並且執行緒數量達到執行緒池最大執行緒數量的時候對新提交的任務所採取的策略,主要有四種策略:AbortPolicy(丟擲異常)、CallerRunsPolicy(只用呼叫者所線上程來執行該任務)、DiscardOldestPolicy(丟掉阻塞佇列中最近的一個任務來處理當前提交的任務)、DiscardPolicy(不做處理,直接丟棄掉)

  ⑥keepAliveTime:存活時間,如果當前執行緒池中的數量比核心執行緒數量多,並且當前執行緒是閒置狀態,該變數就是這些執行緒的最大生存時間

  ⑦TimeUnit:存活時間的時間單位。

  根據上面的引數介紹,簡單瞭解一下執行緒池的實現原理,以提交一個新任務為開始點,分析執行緒池的主要處理流程

3、關於一些執行緒池的使用型別

  ①newFixedThreadPool:建立一個核心執行緒個數和最大執行緒個數均為nThreads的執行緒池,並且阻塞佇列長度為Integer.MAX_VALUE,keepAliveTime=0說明只要執行緒個數比核心執行緒個數多並且當前空閒即回收。

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

  ②newSingleThreadExecutor:建立一個核心執行緒個數和最大執行緒個數都為1 的執行緒池,並且阻塞佇列長度為Integer.MAX_VALUE,keepAliveTime=0說明只要執行緒個數比核心執行緒個數多並且當前執行緒空閒即回收該執行緒。

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

   ③newCachedThreadPoolExecutor:建立一個按需建立執行緒的執行緒池,初始執行緒個數為0,最多執行緒個數為Integer.MAX_VALUE,並且阻塞佇列為同步佇列(最多隻有一個元素),keepAliveTime=60說明只要當前執行緒在60s內空閒則回收。這個型別的執行緒池的特點就是:加入同步佇列的任務會被馬上執行,同步佇列中最多隻有一個任務

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

   4、ThreadPoolExecutor中的其他成員

  其中的ReentrantLock可參考前面寫到的Java中的鎖——Lock和synchronized,其中降到了ReentrantLock的具體實現原理;

  關於AQS部分可參考前面說到的Java中的佇列同步器AQS,也講到了關於AQS的具體實現原理分析;

  關於條件佇列的相關知識可參考前面寫的Java中的執行緒協作之Condition,裡面說到了關於Java中執行緒協作Condition的實現原理;

//獨佔鎖,用來控制新增工作執行緒Worker操作的原子性
private final ReentrantLock mainLock = new ReentrantLock();

//工作執行緒集合,Worker繼承了AQS介面和Runnable介面,是具體處理任務的執行緒物件
//Worker實現AQS,並自己實現了簡單不可重入獨佔鎖,其中state=0表示當前鎖未被獲取狀態,state=1表示鎖被獲取,
//state=-1表示Work建立時候的預設狀態,建立時候設定state=-1是為了防止runWorker方法執行前被中斷
private final HashSet<Worker> workers = new HashSet<Worker>();

//termination是該鎖對應的條件佇列,線上程呼叫awaitTermination時候用來存放阻塞的執行緒
private final Condition termination = mainLock.newCondition();

三、execute(Runnable command)方法實現

  executor方法的作用是提交任務command到執行緒池執行,可以簡單的按照下面的圖進行理解,ThreadPoolExecutor的實現類似於一個生產者消費者模型,當使用者新增任務到執行緒池中相當於生產者生產元素,workers工作執行緒則直接執行任務或者從任務佇列中獲取任務,相當於消費之消費元素。

 1 public void execute(Runnable command) {
 2     //(1)首先檢查任務是否為null,為null丟擲異常,否則進行下面的步驟
 3     if (command == null)
 4         throw new NullPointerException();
 5     //(2)ctl值中包含了當前執行緒池的狀態和執行緒池中的執行緒數量
 6     int c = ctl.get();
 7     //(3)workerCountOf方法是獲取低29位,即獲取當前執行緒池中的執行緒個數,如果小於corePoolSize,就開啟新的執行緒執行
 8     if (workerCountOf(c) < corePoolSize) {
 9         if (addWorker(command, true))
10             return;
11         c = ctl.get();
12     }
13     //(4)如果執行緒池處理RUNNING狀態,就新增任務到阻塞佇列中
14     if (isRunning(c) && workQueue.offer(command)) {
15         //(4-1)二次檢查,獲取ctl值
16         int recheck = ctl.get();
17         //(4-2)如果當前執行緒池不是出於RUNNING狀態,就從佇列中刪除任務,並執行拒絕策略
18         if (! isRunning(recheck) && remove(command))
19             reject(command);
20         //(4-3)否則,如果執行緒池為空,就新增一個執行緒
21         else if (workerCountOf(recheck) == 0)
22             addWorker(null, false);
23     }
24     //(5)如果佇列滿,則新增執行緒,如果新增執行緒失敗,就執行拒絕策略
25     else if (!addWorker(command, false))
26         reject(command);
27 }

  我們在看一下上面程式碼的執行流程,按照標記的數字進行分析:

  ①步驟(3)判斷當前執行緒池中的執行緒個數是否小於corePoolSize,如果小於核心執行緒數,會向workers裡面新增一個核心執行緒執行任務。

  ②如果當前執行緒池中的執行緒數量大於核心執行緒數,就執行(4)。(4)首先判斷當前執行緒池是否處於RUNNING狀態,如果處於該狀態,就新增任務到任務佇列中,這裡需要判斷執行緒池的狀態是因為執行緒池可能已經處於非RUNNING狀態,而在非RUNNING狀態下是需要拋棄新任務的。

  ③如果想任務佇列中新增任務成功,需要進行二次校驗,因為在新增任務到任務佇列後,可能執行緒池的狀態發生了變化,所以這裡需要進行二次校驗,如果當前執行緒池已經不是RUNNING狀態了,需要將任務從任務佇列中移除,然後執行拒絕策略;如果二次校驗通過,則執行4-3程式碼重新判斷當前執行緒池是否為空,如果執行緒池為空沒有執行緒,那麼就需要新建立一個執行緒。

  ④如果上面的步驟(4)建立新增任務失敗,說明佇列已滿,那麼(5)會嘗試再開啟新的執行緒執行任務(類比上圖中的thread3和thread4,即不是核心執行緒的那些執行緒),如果當前執行緒池中的執行緒個數已經大於最大執行緒數maximumPoolSize,表示不能開啟新的執行緒。這就屬於執行緒池滿並且任務佇列滿,就需要執行拒絕策略了。

  下面我們在看看addWorker方法的實現

 1 private boolean addWorker(Runnable firstTask, boolean core) {
 2     retry:
 3     for (;;) {
 4         int c = ctl.get();
 5         int rs = runStateOf(c);
 6 
 7         //(6)檢查佇列是否只在必要時候為空
 8         if (rs >= SHUTDOWN &&
 9             ! (rs == SHUTDOWN &&
10                firstTask == null &&
11                ! workQueue.isEmpty()))
12             return false;
13 
14         //(7)使用CAS增加執行緒個數
15         for (;;) {
16             //根據ctl值獲得當前執行緒池中的執行緒數量
17             int wc = workerCountOf(c);
18             //(7-1)如果執行緒數量超出限制,返回false
19             if (wc >= CAPACITY ||
20                 wc >= (core ? corePoolSize : maximumPoolSize))
21                 return false;
22             //(7-2)CAS增加執行緒數量,同時只有一個執行緒可以成功
23             if (compareAndIncrementWorkerCount(c))
24                 break retry;
25             c = ctl.get();  // 重新讀取ctl值
26             //(7-3)CAS失敗了,需要檢視當前執行緒池狀態是否發生變化,如果發生變化需要跳轉到外層迴圈嘗試重新獲取執行緒池狀態,否則內層迴圈重新進行CAS增加執行緒數量
27             if (runStateOf(c) != rs)
28                 continue retry;
29         }
30     }
31 
32     //(8)執行到這裡說明CAS增加新執行緒個數成功了,我們需要開始建立新的工作執行緒Worker
33     boolean workerStarted = false;
34     boolean workerAdded = false;
35     Worker w = null;
36     try {
37         //(8-1)建立新的worker
38         w = new Worker(firstTask);
39         final Thread t = w.thread;
40         if (t != null) {
41             final ReentrantLock mainLock = this.mainLock;
42             //(8-2)加獨佔鎖,保證workers的同步,可能執行緒池中的多個執行緒呼叫了執行緒池的execute方法
43             mainLock.lock();
44             try {
45                 // (8-3)重新檢查執行緒池狀態,以免在獲取鎖之前呼叫shutdown方法改變執行緒池狀態
46                 int rs = runStateOf(ctl.get());
47 
48                 if (rs < SHUTDOWN ||
49                     (rs == SHUTDOWN && firstTask == null)) {
50                     if (t.isAlive()) // precheck that t is startable
51                         throw new IllegalThreadStateException();
52                     //(8-4)新增新任務
53                     workers.add(w);
54                     int s = workers.size();
55                     if (s > largestPoolSize)
56                         largestPoolSize = s;
57                     workerAdded = true;
58                 }
59             } finally {
60                 mainLock.unlock();
61             }
62             //(8-6)新增新任務成功之後,啟動任務
63             if (workerAdded) {
64                 t.start();
65                 workerStarted = true;
66             }
67         }
68     } finally {
69         if (! workerStarted)
70             addWorkerFailed(w);
71     }
72     return workerStarted;
73 }

  簡單再分析說明一下上面的程式碼,addWorker方法主要分為兩部分,第一部分是使用CAS執行緒安全的新增執行緒數量,第二部分則是建立新的執行緒並且將任務併發安全的新增到新的workers之中,然後啟動執行緒執行。

  ①程式碼(6)中檢查佇列是否只在必要時候為空,只有執行緒池狀態符合條件才能夠進行下面的步驟,從(6)中的判斷條件來看,下面的集中情況addWorker會直接返回false

  ( I )當前執行緒池狀態為STOP,TIDYING或者TERMINATED ; (I I)當前執行緒池狀態為SHUTDOWN並且已經有了第一個任務;   (I I I)當前執行緒池狀態為SHUTDOWN並且任務佇列為空

  ②外層迴圈中判斷條件通過之後,在內層迴圈中使用CAS增加執行緒數,當CAS成功就退出雙重迴圈進行(8)步驟程式碼的執行,如果失敗需要檢視當前執行緒池的狀態是否發生變化,如果發生變化需要進行外層迴圈重新判斷執行緒池狀態然後在進入內層迴圈重新進行CAS增加執行緒數,如果執行緒池狀態沒有發生變化但是上一次CAS失敗就繼續進行CAS嘗試。

  ③執行到(8)程式碼處,表明當前已經成功增加 了執行緒數,但是還沒有執行緒執行任務。ThreadPoolExecutor中使用全域性獨佔鎖mainLock來控制將新增的工作執行緒Worker執行緒安全的新增到工作者執行緒集合workers中。

  ④(8-2)獲取了獨佔鎖,但是在獲取到鎖之後,還需要進行重新檢查執行緒池的狀態,這是為了避免在獲取全域性獨佔鎖之前其他執行緒呼叫了shutDown方法關閉了執行緒池。如果執行緒池已經關閉需要釋放鎖。否則將新增的執行緒新增到工作集合中,釋放鎖啟動執行緒執行任務。

  上面的addWorker方法最後幾行中,會判斷新增工作執行緒是否成功,如果失敗,會執行addWorkerFailed方法,將任務從workers中移除,並且workerCount做-1操作。

 1 private void addWorkerFailed(Worker w) {
 2     final ReentrantLock mainLock = this.mainLock;
 3     //獲取鎖
 4     mainLock.lock();
 5     try {
 6       //如果worker不為null
 7       if (w != null)
 8           //workers移除worker
 9           workers.remove(w);
10       //通過CAS操作,workerCount-1
11       decrementWorkerCount();
12       tryTerminate();
13     } finally {
14       //釋放鎖
15       mainLock.unlock();
16     }
17 }

四、工作執行緒Worker的執行

(1)工作執行緒Worker類原始碼分析

  上面檢視addWorker方法在CAS更新執行緒數成功之後,下面就是建立新的Worker執行緒執行任務,所以我們這裡先檢視Worker類,下面是Worker類的原始碼,我們可以看出,Worker類繼承了AQS並實現了Runnable介面,所以他既是一個自定義的同步元件,也是一個執行任務的執行緒類。下面我們分析Worker類的執行

 1 private final class Worker
 2     extends AbstractQueuedSynchronizer
 3     implements Runnable
 4 {
 5 
 6     /** 使用執行緒工廠建立的執行緒,執行任務 */
 7     final Thread thread;
 8     /** 初始化執行任務 */
 9     Runnable firstTask;
10     /** 計數 */
11     volatile long completedTasks;
12 
13     /**
14      * 給出初始firstTask,執行緒建立工廠建立新的執行緒
15      */
16     Worker(Runnable firstTask) {
17         setState(-1); // 防止在呼叫runWorker之前被中斷
18         this.firstTask = firstTask;
19         this.thread = getThreadFactory().newThread(this); //使用threadFactory建立執行緒
20     }
21 
22     /** run方法實際上執行的是runWorker方法  */
23     public void run() {
24         runWorker(this);
25     }
26 
27     // 關於同步狀態(鎖)
28     //
29     // 同步狀態state=0表示鎖未被獲取
30     // 同步狀態state=1表示鎖被獲取
31 
32     protected boolean isHeldExclusively() {
33         return getState() != 0;
34     }
35 
36     //下面都是重寫AQS的方法,Worker為自定義的同步元件
37     protected boolean tryAcquire(int unused) {
38         if (compareAndSetState(0, 1)) {
39             setExclusiveOwnerThread(Thread.currentThread());
40             return true;
41         }
42         return false;
43     }
44 
45     protected boolean tryRelease(int unused) {
46         setExclusiveOwnerThread(null);
47         setState(0);
48         return true;
49     }
50 
51     public void lock()        { acquire(1); }
52     public boolean tryLock()  { return tryAcquire(1); }
53     public void unlock()      { release(1); }
54     public boolean isLocked() { return isHeldExclusively(); }
55 
56     void interruptIfStarted() {
57         Thread t;
58         if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
59             try {
60                 t.interrupt();
61             } catch (SecurityException ignore) {
62             }
63         }
64     }
65 }

  在建構函式中我們可以看出,首先將同步狀態state置為-1,而Worker這個同步元件的state有三個值,其中state=-1表示Work建立時候的預設狀態,建立時候設定state=-1是為了防止runWorker方法執行前被中斷 前面說到過這個結論,這裡置為-1是為了避免當前Worker在呼叫runWorker方法之前被中斷(當其他執行緒呼叫執行緒池的shutDownNow時候,如果Worker的state>=0則會中斷執行緒),設定為-1就不會被中斷了。而Worker實現Runnable介面,那麼需要重寫run方法,在run方法中,我們可以看到,實際上執行的是runWorker方法,在runWorker方法中,會首先呼叫unlock方法,該方法會將state置為0,所以這個時候呼叫shutDownNow方法就會中斷當前執行緒,而這個時候已經進入了runWork方法了,就不會在還沒有執行runWorker方法的時候就中斷執行緒。

(2)runWorker方法的原始碼分析

 1 final void runWorker(Worker w) {
 2     Thread wt = Thread.currentThread();
 3     Runnable task = w.firstTask;
 4     w.firstTask = null;
 5     w.unlock(); // 這個時候呼叫unlock方法,將state置為0,就可以被中斷了
 6     boolean completedAbruptly = true;
 7     try {
 8         //(10)如果當前任務為null,或者從任務佇列中獲取到的任務為null,就跳轉到(11)處執行清理工作
 9         while (task != null || (task = getTask()) != null) {
10             //task不為null,就需要執行緒執行任務,這個時候,需要獲取工作執行緒內部持有的獨佔鎖
11             w.lock();
12             /**如果執行緒池已被停止(STOP)(至少大於STOP狀態),要確保執行緒都被中斷
13              * 如果狀態不對,檢查當前執行緒是否中斷並清除中斷狀態,並且再次檢查執行緒池狀態是否大於STOP
14              * 如果上述滿足,檢查該物件是否處於中斷狀態,不清除中斷標記
15              */
16             if ((runStateAtLeast(ctl.get(), STOP) ||
17                  (Thread.interrupted() &&
18                   runStateAtLeast(ctl.get(), STOP))) &&
19                 !wt.isInterrupted())
20                 //中斷該物件
21                 wt.interrupt();
22             try {
23                 //執行任務之前要做的事情
24                 beforeExecute(wt, task);
25                 Throwable thrown = null;
26                 try {
27                     task.run(); //執行任務
28                 } catch (RuntimeException x) {
29                     thrown = x; throw x;
30                 } catch (Error x) {
31                     thrown = x; throw x;
32                 } catch (Throwable x) {
33                     thrown = x; throw new Error(x);
34                 } finally {
35                     //執行任務之後的方法
36                     afterExecute(task, thrown);
37                 }
38             } finally {
39                 task = null;
40                 //更新當前已完成任務數量
41                 w.completedTasks++;
42                 //釋放鎖
43                 w.unlock();
44             }
45         }
46         completedAbruptly = false;
47     } finally {
48         //執行清理工作:處理並退出當前worker
49         processWorkerExit(w, completedAbruptly);
50     }
51 }

  我們梳理一下runWorker方法的執行流程

  ①首先先執行unlock方法,將Worker的state置為0,這樣工作執行緒就可以被中斷了(後續的操作如果執行緒池關閉就需要執行緒被中斷)

  ②首先判斷判斷當前的任務(當前工作執行緒中的task,或者從任務佇列中取出的task)是否為null,如果不為null就往下執行,為null就執行processWorkerExit方法。

  ③獲取工作執行緒內部持有的獨佔鎖(避免在執行任務期間,其他執行緒呼叫shutdown後正在執行的任務被中斷,shutdown只會中斷當前被阻塞掛起的沒有執行任務的執行緒)

  ④然後執行beforeExecute()方法,該方法為擴充套件介面程式碼,表示在具體執行任務之前所做的一些事情,然後執行task.run()方法執行具體任務,執行完之後會呼叫afterExecute()方法,用以處理任務執行完畢之後的工作,也是一個擴充套件介面程式碼。

  ⑤更新當前執行緒池完成的任務數,並釋放鎖

(3)執行清理工作的方法processWorkerExit

  下面是方法processWorkerExit的原始碼,在下面的程式碼中

  ①首先(1-1)處統計執行緒池完成的任務個數,並且在此之前獲取全域性鎖,然後更新當前的全域性計數器,然後從工作執行緒集合中移除當前工作執行緒,完成清理工作。

  ②程式碼(1-2)呼叫了tryTerminate 方法,在該方法中,判斷了當前執行緒池狀態是SHUTDOWN並且佇列不為空或者當前執行緒池狀態為STOP並且當前執行緒池中沒有活動執行緒,則置執行緒池狀態為TERMINATED。如果設定稱為了TERMINATED狀態,還需要呼叫全域性條件變數termination的signalAll方法喚醒所有因為呼叫執行緒池的awaitTermination方法而被阻塞住的執行緒,使得執行緒池中的所有執行緒都停止,從而使得執行緒池為TERMINATED狀態。

  ③程式碼(1-3)處判斷當前執行緒池中的執行緒個數是否小於核心執行緒數,如果是,需要新增一個執行緒保證有足夠的執行緒可以執行任務佇列中的任務或者提交的任務。

 1 private void processWorkerExit(Worker w, boolean completedAbruptly) {
 2     /*
 3     *completedAbruptly:是由runWorker傳過來的引數,表示是否突然完成的意思
 4     *當在就是在執行任務過程當中出現異常,就會突然完成,傳true
 5     *
 6     *如果是突然完成,需要通過CAS操作,更新workerCount(-1操作)
 7     *不是突然完成,則不需要-1,因為getTask方法當中已經-1(getTask方法中執行了decrementWorkerCount()方法)
 8     */
 9     if (completedAbruptly)
10         decrementWorkerCount();
11     //(1-1)在統計完成任務個數之前加上全域性鎖,然後統計執行緒池中完成的任務個數並更新全域性計數器,並從工作集中刪除當前worker
12     final ReentrantLock mainLock = this.mainLock;
13     mainLock.lock(); //獲得全域性鎖
14     try {
15         completedTaskCount += w.completedTasks; //更新已完成的任務數量
16         workers.remove(w); //將完成該任務的執行緒worker從工作執行緒集合中移除
17     } finally {
18         mainLock.unlock(); //釋放鎖
19     }
20     /**(1-2)
21      * 這一個方法呼叫完成了下面的事情:
22      * 判斷如果當前執行緒池狀態是SHUTDOWN並且工作佇列為空,
23      * 或者當前執行緒池狀態是STOP並且當前執行緒池裡面沒有活動執行緒,
24      * 則設定當前執行緒池狀態為TERMINATED,如果設定成了TERMINATED狀態,
25      * 還需要呼叫條件變數termination的signAll方法啟用所有因為呼叫執行緒池的awaitTermination方法而被阻塞的執行緒
26      */
27     tryTerminate();
28 
29     //(1-3)如果當前執行緒池中執行緒數小於核心執行緒,則增加核心執行緒數
30     int c = ctl.get();
31     //判斷當前執行緒池的狀態是否小於STOP(RUNNING或者SHUTDOWN)
32     if (runStateLessThan(c, STOP)) {
33         //如果任務忽然完成,執行後續的程式碼
34         if (!completedAbruptly) {
35             //allowCoreThreadTimeOut表示是否允許核心執行緒超時,預設為false
36             //min這裡當預設為allowCoreThreadTimeOut預設為false的時候,min置為coorPoolSize
37             int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
38             //這裡說明:如果允許核心執行緒超時,那麼allowCoreThreadTimeOut可為true,那麼min值為0,不需要維護核心執行緒了
39             //如果min為0並且任務佇列不為空
40             if (min == 0 && ! workQueue.isEmpty())
41                 min = 1; //這裡表示如果min為0,且佇列不為空,那麼至少需要一個核心執行緒存活來保證任務的執行
42             //如果工作執行緒數大於min,表示當前執行緒數滿足,直接返回
43             if (workerCountOf(c) >= min)
44                 return; // replacement not needed
45         }
46         addWorker(null, false);
47     }
48 }

   在tryTerminate 方法中,我們簡單說明了該方法的作用,下面是該方法的原始碼,可以看出原始碼實現上和上面所總結的功能是差不多的

 1 final void tryTerminate() {
 2     for (;;) {
 3         //獲取執行緒池狀態
 4         int c = ctl.get();
 5         //如果執行緒池狀態為RUNNING
 6         //或者狀態大於TIDYING
 7         //或者狀態==SHUTDOWN並未任務佇列不為空
 8         //直接返回,不能呼叫terminated方法
 9         if (isRunning(c) ||
10             runStateAtLeast(c, TIDYING) ||
11             (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
12             return;
13         //如果執行緒池中工作執行緒數不為0,需要中斷執行緒
14         if (workerCountOf(c) != 0) { // Eligible to terminate
15             interruptIdleWorkers(ONLY_ONE);
16             return;
17         }
18         //獲得執行緒池的全域性鎖
19         final ReentrantLock mainLock = this.mainLock;
20         mainLock.lock();
21         try {
22             //通過CAS操作,將執行緒池狀態設定為TIDYING
23             if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) { //private static int ctlOf(int rs, int wc) { return rs | wc; }
24                 try {
25                     //呼叫terminated方法
26                     terminated();
27                 } finally {
28                     //最終將執行緒狀態設定為TERMINATED
29                     ctl.set(ctlOf(TERMINATED, 0));
30                     //呼叫條件變數termination的signaAll方法喚醒所有因為
31                     //呼叫執行緒池的awaitTermination方法而被阻塞的執行緒
32                     //private final Condition termination = mainLock.newCondition();
33                     termination.signalAll();
34                 }
35                 return;
36             }
37         } finally {
38             mainLock.unlock();
39         }
40         // else retry on failed CAS
41     }
42 }

 五、補充(shutdown、shutdownNow、awaitTermination方法)

(1)shutdown操作

  我們在使用執行緒池的時候知道,呼叫shutdown方法之後執行緒池就不會再接受新的任務了,但是任務佇列中的任務還是需要執行完的。呼叫該方法會立刻返回,並不是等到執行緒池的任務佇列中的所有任務執行完畢在返回的。

 1 public void shutdown() {
 2     //獲得執行緒池的全域性鎖
 3     final ReentrantLock mainLock = this.mainLock;
 4     mainLock.lock();
 5     try {
 6         //進行許可權檢查
 7         checkShutdownAccess();
 8         
 9         //設定當前執行緒池的狀態的SHUTDOWN,如果執行緒池狀態已經是該狀態就會直接返回,下面我們會分析這個方法的原始碼
10         advanceRunState(SHUTDOWN);
11         
12         //設定中斷 標誌
13         interruptIdleWorkers();
14         onShutdown(); // hook for ScheduledThreadPoolExecutor
15     } finally {
16         mainLock.unlock();
17     }
18     //嘗試將狀態變為TERMINATED,上面已經分析過該方法的原始碼
19     tryTerminate();
20 }

  該方法的原始碼比較簡短,首先檢查了安全管理器,是檢視當前呼叫shutdown命令的執行緒是否有關閉執行緒的許可權,如果有許可權還需要看呼叫執行緒是否有中斷工作執行緒的許可權,如果沒有許可權將會丟擲SecurityException異常或者空指標異常。下面我們檢視一下advanceRunState   方法的原始碼。

 1 private void advanceRunState(int targetState) {
 2     for (;;) {
 3         //下面的方法執行的就是:
 4         //首先獲取執行緒的ctl值,然後判斷當前執行緒池的狀態如果已經是SHUTDOWN,那麼if條件第一個為真就直接返回
 5         //如果不是SHUTDOWN狀態,就需要CAS的設定當前狀態為SHUTDOWN
 6         int c = ctl.get();
 7         if (runStateAtLeast(c, targetState) ||
 8             //private static int ctlOf(int rs, int wc) { return rs | wc; }
 9             ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))
10             break;
11     }
12 }

  我們可以看出advanceRunState   方法實際上就是判斷當前執行緒池的狀態是否為SHUTDWON,如果是那麼就返回,否則就需要設定當前狀態為SHUTDOWN。

  我們再來看看shutdown方法中呼叫執行緒中斷的方法interruptIdleWorkers原始碼

 1 private void interruptIdleWorkers() {
 2     interruptIdleWorkers(false);
 3 }
 4 private void interruptIdleWorkers(boolean onlyOne) {
 5     final ReentrantLock mainLock = this.mainLock;
 6     mainLock.lock();
 7     try {
 8         for (Worker w : workers) {
 9             Thread t = w.thread;
10             //如果工作執行緒沒有被中斷,並且沒有正在執行設定中斷標誌
11             if (!t.isInterrupted() && w.tryLock()) {
12                 try {
13                     //需要中斷當前執行緒
14                     t.interrupt();
15                 } catch (SecurityException ignore) {
16                 } finally {
17                     w.unlock();
18                 }
19             }
20             if (onlyOne)
21                 break;
22         }
23     } finally {
24         mainLock.unlock();
25     }
26 }

  上面的程式碼中,需要設定所有空閒執行緒的中斷標誌。首先獲取執行緒池的全域性鎖,同時只有一個執行緒可以呼叫shutdown方法設定中斷標誌。然後嘗試獲取工作執行緒Worker自己的鎖,獲取成功則可以設定中斷標誌(這是由於正在執行任務的執行緒需要獲取自己的鎖,並且不可重入,所以正在執行的任務沒有被中斷),這裡要中斷的那些執行緒是阻塞到getTask()方法並嘗試從任務佇列中獲取任務的執行緒即空閒執行緒。

(2)shutdownNow操作

   在使用執行緒池的時候,如果我們呼叫了shutdownNow方法,執行緒池不僅不會再接受新的任務,還會將任務佇列中的任務丟棄,正在執行的任務也會被中斷,然後立刻返回該方法,不會等待啟用的任務完成,返回值為當前任務佇列中被丟棄的任務列表

 1 public List<Runnable> shutdownNow() {
 2     List<Runnable> tasks;
 3     final ReentrantLock mainLock = this.mainLock;
 4     mainLock.lock();
 5     try {
 6         checkShutdownAccess(); //還是進行許可權檢查
 7         advanceRunState(STOP); //設定執行緒池狀態臺STOP
 8         interruptWorkers(); //中斷所有執行緒
 9         tasks = drainQueue(); //將任務佇列中的任務移動到task中
10     } finally {
11         mainLock.unlock();
12     }
13     tryTerminate();
14     return tasks; //返回tasks
15 }

   從上面的程式碼中,我們可以可以發現,shutdownNow方法也是首先需要檢查呼叫該方法的執行緒的許可權,之後不同於shutdown方法之處在於需要即刻設定當前執行緒池狀態為STOP,然後中斷所有執行緒(空閒執行緒+正在執行任務的執行緒),移除任務佇列中的任務

 1 private void interruptWorkers() {
 2     final ReentrantLock mainLock = this.mainLock;
 3     mainLock.lock();
 4     try {
 5         for (Worker w : workers) //不需要判斷當前執行緒是否在執行任務(即不需要呼叫w.tryLock方法),中斷所有執行緒
 6             w.interruptIfStarted();
 7     } finally {
 8         mainLock.unlock();
 9     }
10 }

(3)awaitTermination操作

   當執行緒呼叫該方法之後,會阻塞呼叫者執行緒,直到執行緒池狀態為TERMINATED狀態才會返回,或者等到超時時間到之後會返回,下面是該方法的原始碼。

 1 //呼叫該方法之後,會阻塞呼叫者執行緒,直到執行緒池狀態為TERMINATED狀態才會返回,或者等到超時時間到之後會返回
 2 public boolean awaitTermination(long timeout, TimeUnit unit)
 3     throws InterruptedException {
 4     long nanos = unit.toNanos(timeout);
 5     final ReentrantLock mainLock = this.mainLock;
 6     mainLock.lock();
 7     try {
 8         //阻塞當前執行緒,(獲取了Worker自己的鎖),那麼當前執行緒就不會再執行任務(因為獲取不到鎖)
 9         for (;;) {
10             //當前執行緒池狀態為TERMINATED狀態,會返回true
11             if (runStateAtLeast(ctl.get(), TERMINATED)) 
12                 return true;
13             //超時時間到返回false
14             if (nanos <= 0) 
15                 return false;
16             nanos = termination.awaitNanos(nanos);
17         }
18     } finally {
19         mainLock.unlock();
20     }
21 }

  在上面的程式碼中,呼叫者執行緒需要首先獲取執行緒Worker 自己的獨佔鎖,然後在迴圈判斷當前執行緒池是否已經是TERMINATED狀態,如果是則直接返回,否則說明當前執行緒池中還有執行緒正在執行任務,這時候需要檢視當前設定的超時時間是否小於0,小於0說明不需要等待就直接返回,如果大於0就需要呼叫條件變數termination的awaitNanos方法等待設定的時間,並在這段時間之內等待執行緒池的狀態變為TERMINATED。

  我們在前面說到清理執行緒池的方法processWorkerExit的時候,需要呼叫tryTerminated方法,在該方法中會檢視當前執行緒池狀態是否為TERMINATED,如果是該狀態也會呼叫termination.signalAll()方法喚醒所有執行緒池中因呼叫awaitTermination而被阻塞住的執行緒。

  如果是設定了超時時間,那麼termination的awaitNanos方法也會返回,這時候需要重新檢查執行緒池狀態是否為TERMINATED,如果是則返回,不是就繼續阻塞自己。

相關文章