0.綜述
- ctl 是線程池源碼中常常用到的一個變量。
- 它的主要作用是記錄線程池的生命周期狀態和當前工作的線程數。
- 作者通過巧妙的設計,將一個整型變量按二進制位分成兩部分,分別表示兩個信息。
1.聲明與初始化
源碼:
1 private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
分析一波:
- ctl (線程池控制狀態)是原子整型的,這意味這對它進行的操作具有原子性。
- 如此一來,作為 ctl 組成部分的 runState (線程池生命周期狀態)和 workerCount (工作線程數) 也將同時具有原子性。
- 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;
分析一波:
- COUNT_BITS 常量的值為 Integer.SIZE - 3 ,其中 Integer.SIZE 為整型最大位數,在本文剩余部分,我們取其為 32 。
- 如此 COUNT_BITS 實際的值其實就是 29 。這里有些讀者可能會有 “為什么減去的數是 3 而不是別的” 的疑惑,這將在后文得到解答。
- CAPACITY 常量的值為 (1 << COUNT_BITS) - 1 ,其中 << 為左移運算符,這么說可能不太直觀,我以二進制直接寫出這個數將有助於理解:
1 0000 0000 0000 0001 1 << 29 - 1 0001 1111 1111 1111
- 因此在接下來的代碼中, COUNT_BITS 就用來表示分隔runState 和workerCount 的位數;
- 而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;
分析一波:
- 這里解答了上邊關於 COUNT_BITS 變量為什么要減 3 的問題:因為線程池的生命周期有 5 個狀態,為了表達這 5 個狀態,我們需要 3 個二進制位。
- 對線程池的生命周期有興趣的讀者請百度 線程池生命周期 ;不明白為什么 5 個狀態需要 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; }
分析一波:
- 此處我們解答了 CAPACITY 常量的作用,之前提到過,他是一個后 29 位均為 1 ,前 3 位為 0 的整數,因此我們可以通過:
- 對 CAPACITY 和 ctl 進行 & (按位與)操作就能取到 ctl 的后 29 位,即 workerCount 。
- 對 CAPACITY 進行 ~ (按位取反)操作后,再和 ctl 進行 & 操作就能取到 runState 。它的高 3 位是 ctl 的高 3 位,低 29 位為 0。這也解釋了為什么之前提到的生命周期常量要在 -1 ~ 3 的基礎上再左移 29 位,因為不在常量初始化處左移的話就要在拆包的時候右移來保證取到的是正確的數值。然而拆包操作是要經常進行的,而常量的初始化只有一次。兩下對比,明顯在初始化時左移是效率更高的選擇。
- 除了拆包時的效率,常量初始化時左移也提高了打包函數的效率:此處打包函數可以直接對 runState 和 workerCount 進行 | (按位或)操作來得到 ctl 變量,就是因為 runState 的高 3 位為有效信息,而 workerCount 的低 29 位為有效信息,合起來正好得到一個含 32 位有效信息的整型變量。
- 說到這里可能仍有些讓人疑惑,我將再以二進制的形式表示出所有涉及到的變量/常量:
//下文中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 }
分析一波:
- 注意這里傳入的s是用了之前定義的生命周期常量。
- 這里判斷狀態的大小時,直接將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 }
分析一波:
- 注意到這里的修改都使用了原子整型的CAS方法。
7.修改ctl中runState的大小
源碼:
1 ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c)))
分析一波:
- 注意到修改 runState 並沒有再提供專門的方法,而是直接使用了原子整型的CAS方法來替換原來的 ctl 。
8.仍存在的疑問
- Q1:如果經過遞增 compareAndIncrementWorkerCount ,使得 workerCount 的大小超過29位,會發生什么?會有安全檢查嗎?
- A1:有安全檢查,在ThreadPoolExecutor類的addWorker方法中有這樣一行代碼:
1 if (wc >= CAPACITY || 2 wc >= (core ? corePoolSize : maximumPoolSize)) 3 return false;
- Q2:為什么為 workerCount 的修改提供了方法,卻沒有為 runState 的修改提供?