詳解Java線程池的ctl(線程池控制狀態(tài))【源碼分析】
點擊上方藍(lán)色字體,選擇“標(biāo)星公眾號”
優(yōu)質(zhì)文章,第一時間送達(dá)
? 作者?|?moonfair?
來源 | urlify.cn/JFfe6j
0.綜述
?ctl?是線程池源碼中常常用到的一個變量。
它的主要作用是記錄線程池的生命周期狀態(tài)和當(dāng)前工作的線程數(shù)。
作者通過巧妙的設(shè)計,將一個整型變量按二進(jìn)制位分成兩部分,分別表示兩個信息。
1.聲明與初始化
源碼:
1?private?final?AtomicInteger ctl = new?AtomicInteger(ctlOf(RUNNING, 0));分析一波:
?ctl?(線程池控制狀態(tài))是原子整型的,這意味這對它進(jìn)行的操作具有原子性。
如此一來,作為?ctl?組成部分的?runState?(線程池生命周期狀態(tài))和?workerCount?(工作線程數(shù)) 也將同時具有原子性。
?ThreadPoolExecutor?使用 ?ctlOf?方法來將 ?runState?和 ?workerCount?兩個變量(都是整型)打包成一個?ctl? 變量。稍后將解讀這個方法的實現(xiàn)。
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?為整型最大位數(shù),在本文剩余部分,我們?nèi)∑錇?32?。
如此?COUNT_BITS?實際的值其實就是?29?。這里有些讀者可能會有 “為什么減去的數(shù)是 3 而不是別的” 的疑惑,這將在后文得到解答。
CAPACITY? 常量的值為 ?(1 << COUNT_BITS) - 1?,其中?<<?為左移運算符,這么說可能不太直觀,我以二進(jìn)制直接寫出這個數(shù)將有助于理解:
1
0000 0000 0000 0001
1 << 29 - 1
0001 1111 1111 1111因此在接下來的代碼中,?COUNT_BITS?就用來表示分隔runState?和workerCount?的位數(shù);
而CAPACITY?則作為取這兩個變量(?runState?和?workerCount?)的工具(具體是怎么使用的請看下文)
3.線程池生命周期狀態(tài)常量
? 源碼:
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;分析一波:
這里解答了上邊關(guān)于?COUNT_BITS?變量為什么要減 3 的問題:因為線程池的生命周期有 5 個狀態(tài),為了表達(dá)這 5 個狀態(tài),我們需要 3 個二進(jìn)制位。
對線程池的生命周期有興趣的讀者請百度?線程池生命周期?;不明白為什么 5 個狀態(tài)需要 3 個二進(jìn)制位的請百度?二進(jìn)制?。
注意到這里標(biāo)注狀態(tài)使用的并不是?-1 ~ 3?,而是這 5 個數(shù)字分別左移?COUNT_BITS?位,這樣做的好處將在接下來的代碼中得到體現(xiàn)。
4.打包函數(shù)與拆包函數(shù)
源碼:
1?//拆包函數(shù)
2?private?static?int?runStateOf(int?c)?????{ return?c & ~CAPACITY; }
3?private?static?int?workerCountOf(int?c)??{ return?c & CAPACITY; }
4?//打包函數(shù)
5?private?static?int?ctlOf(int?rs, int?wc)?{ return?rs | wc; }分析一波:
此處我們解答了?CAPACITY?常量的作用,之前提到過,他是一個后 29 位均為 1 ,前 3 位為 0 的整數(shù),因此我們可以通過:
對?CAPACITY?和?ctl?進(jìn)行?&?(按位與)操作就能取到?ctl?的后 29 位,即??workerCount?。
對?CAPACITY?進(jìn)行?~?(按位取反)操作后,再和?ctl?進(jìn)行?&?操作就能取到?runState?。它的高 3 位是?ctl?的高 3 位,低 29 位為 0。這也解釋了為什么之前提到的生命周期常量要在?-1 ~ 3?的基礎(chǔ)上再左移 29 位,因為不在常量初始化處左移的話就要在拆包的時候右移來保證取到的是正確的數(shù)值。然而拆包操作是要經(jīng)常進(jìn)行的,而常量的初始化只有一次。兩下對比,明顯在初始化時左移是效率更高的選擇。
除了拆包時的效率,常量初始化時左移也提高了打包函數(shù)的效率:此處打包函數(shù)可以直接對?runState?和?workerCount?進(jìn)行?|?(按位或)操作來得到?ctl?變量,就是因為?runState?的高 3 位為有效信息,而?workerCount?的低 29 位為有效信息,合起來正好得到一個含 32 位有效信息的整型變量。
說到這里可能仍有些讓人疑惑,我將再以二進(jìn)制的形式表示出所有涉及到的變量/常量:
//下文中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.運行狀態(tài)的判斷
源碼:
private?static?boolean?runStateLessThan(int?c, int?s)?{
????????return?c < s;
????}
????private?static?boolean?runStateAtLeast(int?c, int?s)?{
????????return?c >= s;
????}
????private?static?boolean?isRunning(int?c)?{
????????return?c < SHUTDOWN;
????}分析一波:
注意這里傳入的s是用了之前定義的生命周期常量。
這里判斷狀態(tài)的大小時,直接將c?和s?進(jìn)行了比較,這是因為代表狀態(tài)的信息占據(jù)了兩個變量的高 3 位,而比較高位的大小時,低位是沒有影響的。
6.修改ctl中workCount的大小
源碼:
private?boolean?compareAndIncrementWorkerCount(int?expect)?{
????????return?ctl.compareAndSet(expect, expect + 1);
????}
????private?boolean?compareAndDecrementWorkerCount(int?expect)?{
????????return?ctl.compareAndSet(expect, expect - 1);
????}
????private?void?decrementWorkerCount()?{
????????do?{} while?(! compareAndDecrementWorkerCount(ctl.get()));
????}分析一波:
注意到這里的修改都使用了原子整型的CAS方法。
7.修改ctl中runState的大小
源碼:
1?ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c)))分析一波:
注意到修改?runState?并沒有再提供專門的方法,而是直接使用了原子整型的CAS方法來替換原來的?ctl?。
?8.仍存在的疑問
Q1:如果經(jīng)過遞增?compareAndIncrementWorkerCount?,使得?workerCount?的大小超過29位,會發(fā)生什么?會有安全檢查嗎?
Q2:為什么為?workerCount?的修改提供了方法,卻沒有為?runState?的修改提供?
粉絲福利:108本java從入門到大神精選電子書領(lǐng)取
???
?長按上方二維碼?2 秒 回復(fù)「1234」即可獲取資料以及 可以進(jìn)入java1234官方微信群
感謝點贊支持下哈?
