詳解Java線程池的ctl(線程池控制狀態)【源碼分析】

0.綜述

  1.  ctl 是線程池源碼中常常用到的一個變量。
  2. 它的主要作用是記錄線程池的生命週期狀態和當前工作的線程數。
  3. 作者通過巧妙的設計,將一個整型變量按二進制位分成兩部分,分別表示兩個信息。

1.聲明與初始化

  源碼:

1 private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

  分析一波:

  1.  ctl (線程池控制狀態)是原子整型的,這意味這對它進行的操作具有原子性。
  2. 如此一來,作爲 ctl 組成部分的 runState (線程池生命週期狀態)和 workerCount (工作線程數) 也將同時具有原子性。
  3.  ThreadPoolExecutor 使用  ctlOf 方法來將  runState 和  workerCount 兩個變量(都是整型)打包成一個 ctl  變量。稍後將解讀這個方法的實現。

2.兩個工具人常量 COUNT_BITS 和 CAPACITY 

  源碼:

1 private static final int COUNT_BITS = Integer.SIZE - 3;
2 private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

  分析一波:

  1. COUNT_BITS  常量的值爲 Integer.SIZE - 3 ,其中 Integer.SIZE 爲整型最大位數,在本文剩餘部分,我們取其爲 32 。
  2. 如此 COUNT_BITS 實際的值其實就是 29 。這裏有些讀者可能會有 “爲什麼減去的數是 3 而不是別的” 的疑惑,這將在後文得到解答。
  3. CAPACITY  常量的值爲  (1 << COUNT_BITS) - 1 ,其中 << 爲左移運算符,這麼說可能不太直觀,我以二進制直接寫出這個數將有助於理解:
    1
    0000 0000 0000 0001
    1 << 29 - 1
    0001 1111 1111 1111
  4. 因此在接下來的代碼中, COUNT_BITS 就用來表示分隔runState 和workerCount 的位數;
  5. CAPACITY 則作爲取這兩個變量( runState 和 workerCount )的工具(具體是怎麼使用的請看下文)

3.線程池生命週期狀態常量

   源碼:

1 private static final int RUNNING    = -1 << COUNT_BITS;
2 private static final int SHUTDOWN   =  0 << COUNT_BITS;
3 private static final int STOP       =  1 << COUNT_BITS;
4 private static final int TIDYING    =  2 << COUNT_BITS;
5 private static final int TERMINATED =  3 << COUNT_BITS;

  分析一波:

  1. 這裏解答了上邊關於 COUNT_BITS 變量爲什麼要減 3 的問題:因爲線程池的生命週期有 5 個狀態,爲了表達這 5 個狀態,我們需要 3 個二進制位。
  2. 線程池的生命週期有興趣的讀者請百度 線程池生命週期 ;不明白爲什麼 5 個狀態需要 3 個二進制位的請百度 二進制 。
  3. 注意到這裏標註狀態使用的並不是 -1 ~ 3 ,而是這 5 個數字分別左移 COUNT_BITS 位,這樣做的好處將在接下來的代碼中得到體現。

4.打包函數與拆包函數

  源碼:

1 //拆包函數
2 private static int runStateOf(int c)     { return c & ~CAPACITY; }
3 private static int workerCountOf(int c)  { return c & CAPACITY; }
4 //打包函數
5 private static int ctlOf(int rs, int wc) { return rs | wc; }

  分析一波:

  1. 此處我們解答了 CAPACITY 常量的作用,之前提到過,他是一個後 29 位均爲 1 ,前 3 位爲 0 的整數,因此我們可以通過:
  2. 對 CAPACITY 和 ctl 進行 & (按位與)操作就能取到 ctl 的後 29 位,即  workerCount 。
  3. 對 CAPACITY 進行 ~ (按位取反)操作後,再和 ctl 進行 & 操作就能取到 runState 。它的高 3 位是 ctl 的高 3 位,低 29 位爲 0。這也解釋了爲什麼之前提到的生命週期常量要在 -1 ~ 3 的基礎上再左移 29 位,因爲不在常量初始化處左移的話就要在拆包的時候右移來保證取到的是正確的數值。然而拆包操作是要經常進行的,而常量的初始化只有一次。兩下對比,明顯在初始化時左移是效率更高的選擇。
  4. 除了拆包時的效率,常量初始化時左移也提高了打包函數的效率:此處打包函數可以直接對 runState 和 workerCount 進行 | (按位或)操作來得到 ctl 變量,就是因爲 runState 的高 3 位爲有效信息,而 workerCount 的低 29 位爲有效信息,合起來正好得到一個含 32 位有效信息的整型變量。
  5. 說到這裏可能仍有些讓人疑惑,我將再以二進制的形式表示出所有涉及到的變量/常量:
    //下文中a和b分別代表runState和workerCount的有效信息
    
    //CAPACITY
    0001 1111 1111 1111
    //ctl
    aaab bbbb bbbb bbbb
    //runState
    aaa0 0000 0000 0000
    //workerCount
    000b bbbb bbbb bbbb

     

5.運行狀態的判斷

  源碼:

 1     private static boolean runStateLessThan(int c, int s) {
 2         return c < s;
 3     }
 4 
 5     private static boolean runStateAtLeast(int c, int s) {
 6         return c >= s;
 7     }
 8 
 9     private static boolean isRunning(int c) {
10         return c < SHUTDOWN;
11     }

 

  分析一波:

  1. 注意這裏傳入的s是用了之前定義的生命週期常量。
  2. 這裏判斷狀態的大小時,直接將c 和s 進行了比較,這是因爲代表狀態的信息佔據了兩個變量的高 3 位,而比較高位的大小時,低位是沒有影響的。

6.修改ctl中workCount的大小

  源碼:

 1     private boolean compareAndIncrementWorkerCount(int expect) {
 2         return ctl.compareAndSet(expect, expect + 1);
 3     }
 4 
 5     private boolean compareAndDecrementWorkerCount(int expect) {
 6         return ctl.compareAndSet(expect, expect - 1);
 7     }
 8 
 9     private void decrementWorkerCount() {
10         do {} while (! compareAndDecrementWorkerCount(ctl.get()));
11     }

  分析一波:

  1. 注意到這裏的修改都使用了原子整型的CAS方法。

7.修改ctl中runState的大小

  源碼:

1 ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c)))

  分析一波:

  1. 注意到修改 runState 並沒有再提供專門的方法,而是直接使用了原子整型的CAS方法來替換原來的 ctl 。

 8.仍存在的疑問

  • Q1:如果經過遞增 compareAndIncrementWorkerCount ,使得 workerCount 的大小超過29位,會發生什麼?會有安全檢查嗎?
  • Q2:爲什麼爲 workerCount 的修改提供了方法,卻沒有爲 runState 的修改提供?
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章