為什么要使用位運(yùn)算表示線程池狀態(tài)?
我們閱讀ThreadPoolExecutor源碼時在開篇就會發(fā)現(xiàn)很多位運(yùn)算代碼:
public?class?ThreadPoolExecutor?extends?AbstractExecutorService?{
????private?final?AtomicInteger?ctl?=?new?AtomicInteger(ctlOf(RUNNING,?0));
????private?static?final?int?COUNT_BITS?=?Integer.SIZE?-?3;
????private?static?final?int?CAPACITY???=?(1?<<?COUNT_BITS)?-?1;
????private?static?final?int?RUNNING????=?-1?<<?COUNT_BITS;
????private?static?final?int?SHUTDOWN???=??0?<<?COUNT_BITS;
????private?static?final?int?STOP???????=??1?<<?COUNT_BITS;
????private?static?final?int?TIDYING????=??2?<<?COUNT_BITS;
????private?static?final?int?TERMINATED?=??3?<<?COUNT_BITS;
????private?static?int?runStateOf(int?c)?????{?return?c?&?~CAPACITY;?}
????private?static?int?workerCountOf(int?c)??{?return?c?&?CAPACITY;?}
????private?static?int?ctlOf(int?rs,?int?wc)?{?return?rs?|?wc;?}
}
不難發(fā)現(xiàn)線程狀態(tài)都用位運(yùn)算表示,但是為什么要這樣做呢?為什么不定義為直觀的數(shù)字呢?下面我們進(jìn)行分析。雖然代碼量不多,但是想要理解線程池就必須要理解為什么使用位運(yùn)算。
ThreadPoolExecutor在設(shè)計(jì)時就是用一個int數(shù)值表示了兩個業(yè)務(wù)含義:線程池狀態(tài)和線程數(shù)量。其中高3位表示線程池狀態(tài),低29位表示線程數(shù)量,這個設(shè)計(jì)思想體現(xiàn)在以下三句代碼:
//?代碼1
private?final?AtomicInteger?ctl?=?new?AtomicInteger(ctlOf(RUNNING,?0));
//?代碼2
private?static?final?int?COUNT_BITS?=?Integer.SIZE?-?3;
//?代碼3
private?static?final?int?CAPACITY???=?(1?<<?COUNT_BITS)?-?1;
代碼1表示用一個int保存線程池信息,代碼2表示一共有(32-3)=29位可以表示線程數(shù)量,代碼3表示理論上最大線程數(shù)量為536870911,這個理論值足以支撐線程池使用。
1 線程池狀態(tài)
我們明白了線程池上述設(shè)計(jì)思想,下面就來分析線程池狀態(tài)值:
private?static?final?int?RUNNING????=?-1?<<?COUNT_BITS;
private?static?final?int?SHUTDOWN???=??0?<<?COUNT_BITS;
private?static?final?int?STOP???????=??1?<<?COUNT_BITS;
private?static?final?int?TIDYING????=??2?<<?COUNT_BITS;
private?static?final?int?TERMINATED?=??3?<<?COUNT_BITS;
我們知道COUNT_BITS=29則上述代碼等價(jià)于:
private?static?final?int?RUNNING????=?-1?<<?29;
private?static?final?int?SHUTDOWN???=??0?<<?29;
private?static?final?int?STOP???????=??1?<<?29;
private?static?final?int?TIDYING????=??2?<<?29;
private?static?final?int?TERMINATED?=??3?<<?29;
現(xiàn)在我們算一算這些狀態(tài)等于多少,在這里我們從后往前算,因?yàn)镽UNNING狀態(tài)是負(fù)數(shù)左移運(yùn)算,計(jì)算步驟稍微多一些。
(1) TERMINATED = 3 << 29
初始值:00000000?00000000?00000000?00000011
左移后:01100000?00000000?00000000?00000000
(2) TIDYING = 2 << 29
初始值:00000000?00000000?00000000?00000010
左移后:01000000?00000000?00000000?00000000
(3) STOP = 1 << 29
初始值:00000000?00000000?00000000?00000001
左移后:00100000?00000000?00000000?00000000
(4) SHUTDOWN = 0 << 29
初始值:00000000?00000000?00000000?00000000
左移后:00000000?00000000?00000000?00000000
(5) RUNNING = -1 << 29
原碼:10000000?00000000?00000000?00000001
反碼:11111111?11111111?11111111?11111110(原碼符號位不變、數(shù)值位取反)
補(bǔ)碼:11111111?11111111?11111111?11111111(反碼+1)
左移:11100000?00000000?00000000?00000000
2 位運(yùn)算應(yīng)用
runStateOf方法用來獲取線程池狀態(tài)信息,workerCountOf方法用來獲取線程池線程數(shù),ctlOf方法用來設(shè)置當(dāng)前線程池狀態(tài)和線程數(shù)量信息,我們分別進(jìn)行計(jì)算。
private?static?int?runStateOf(int?c)?????{?return?c?&?~CAPACITY;?}
private?static?int?workerCountOf(int?c)??{?return?c?&?CAPACITY;?}
private?static?int?ctlOf(int?rs,?int?wc)?{?return?rs?|?wc;?}
(1) CAPACITY ? = (1 << 29) - 1
先左移:00100000?00000000?00000000?00000000
再減一:00011111?11111111?11111111?11111111
(2) ~CAPACITY
原始值:00011111?11111111?11111111?11111111
取反后:11100000?00000000?00000000?00000000
(3) runStateOf
現(xiàn)在一個線程池狀態(tài)是RUNNING并且線程數(shù)量等于3用二進(jìn)制表示如下:
11100000?00000000?00000000?00000011
執(zhí)行runStateOf方法就可以得到線程池狀態(tài):
11100000?00000000?00000000?00000011
&
11100000?00000000?00000000?00000000
=
11100000?00000000?00000000?00000000
(4) workerCountOf
現(xiàn)在一個線程池狀態(tài)是RUNNING并且線程數(shù)量等于4用二進(jìn)制表示如下:
11100000?00000000?00000000?00000100
執(zhí)行workerCountOf方法就可以得到線程數(shù)量:
11100000?00000000?00000000?00000100
&
00011111?11111111?11111111?11111111
=
00000000?00000000?00000000?00000100
(5) ctlOf
現(xiàn)在我們要設(shè)置一個狀態(tài)是RUNNING且線程數(shù)量等于4的線程池ctl值:
private?final?AtomicInteger?ctl?=?new?AtomicInteger(ctlOf(RUNNING,?4));
11100000?00000000?00000000?00000000
|
00000000?00000000?00000000?00000100
=
11100000?00000000?00000000?00000100
這個方法真正體現(xiàn)了高3位表示線程池狀態(tài),低29位表示線程數(shù)量這個設(shè)計(jì)思想優(yōu)點(diǎn),原本需要兩步設(shè)置動作現(xiàn)在只需要一步,從而實(shí)現(xiàn)了操作原子性,這樣就可以滿足線程池的很多CAS操作,例如線程池在調(diào)用addWorker新增工作線程數(shù)時會調(diào)用compareAndIncrementWorkerCount方法增加線程數(shù)量。
但是假設(shè)同一時刻shutdownNow方法導(dǎo)致線程池狀態(tài)發(fā)生改變,那么新增工作線程數(shù)方法就不會調(diào)用成功,需要繼續(xù)執(zhí)行自旋進(jìn)行嘗試,這體現(xiàn)了線程狀態(tài)和線程數(shù)量維護(hù)的原子性。
3 位圖法應(yīng)用
3.1 需求背景
我們看看位運(yùn)算怎樣應(yīng)用在實(shí)際開發(fā)場景。假設(shè)在系統(tǒng)中用戶一共有三種角色:普通用戶、管理員、超級管理員,現(xiàn)在需要設(shè)計(jì)一張用戶角色表記錄這類信息。我們不難設(shè)計(jì)出如下方案:
| id | name | super | admin | normal |
|---|---|---|---|---|
| 101 | 用戶一 | 1 | 0 | 0 |
| 102 | 用戶二 | 0 | 1 | 0 |
| 103 | 用戶三 | 0 | 0 | 1 |
| 104 | 用戶四 | 1 | 1 | 1 |
我們使用1表示是,0表示否,那么觀察上表不難得出,用戶一有用超級管理員角色,用戶二具有管理員角色,用戶三具有普通用戶角色,用戶四同時具有三種角色。如果此時新增加一種角色呢?那么新增一個字段即可。
3.2 發(fā)現(xiàn)問題
按照上述一個字段表示一種角色進(jìn)行表設(shè)計(jì)功能上是沒有問題的,優(yōu)點(diǎn)是容易理解結(jié)構(gòu)清晰,但是我們想一想有沒有什么問題?筆者遇到過如下問題:在復(fù)雜業(yè)務(wù)環(huán)境一份數(shù)據(jù)可能會使用在不同的場景,例如上述數(shù)據(jù)存儲在MySQL數(shù)據(jù)庫,這一份數(shù)據(jù)還會被用在如下場景:
檢索數(shù)據(jù)需要同步一份到ES
業(yè)務(wù)方使用此表通過Flink計(jì)算業(yè)務(wù)指標(biāo)
業(yè)務(wù)方訂閱此表Binlog消息進(jìn)行業(yè)務(wù)處理
如果表結(jié)構(gòu)發(fā)生變化,數(shù)據(jù)源之間就要重新進(jìn)行對接,業(yè)務(wù)方也要進(jìn)行代碼修改,這樣開發(fā)成本比較非常高。有沒有辦法避免此類問題?
3.3 解決方案
我們可以使用位圖法,這樣同一個字段可以表示多個業(yè)務(wù)含義。首先設(shè)計(jì)如下數(shù)據(jù)表,userFlag字段暫時不填。
| id | name | user_flag |
|---|---|---|
| 101 | 用戶一 | 暫時不填 |
| 102 | 用戶二 | 暫時不填 |
| 103 | 用戶三 | 暫時不填 |
| 104 | 用戶四 | 暫時不填 |
我們設(shè)計(jì)位圖每一個bit表示一種角色:

我們使用位圖法表示如下數(shù)據(jù)表:
| id | name | super | admin | normal |
|---|---|---|---|---|
| 101 | 用戶一 | 1 | 0 | 0 |
| 102 | 用戶二 | 0 | 1 | 0 |
| 103 | 用戶三 | 0 | 0 | 1 |
| 104 | 用戶四 | 1 | 1 | 1 |
用戶一位圖如下十進(jìn)制數(shù)值等于4:

用戶二位圖如下十進(jìn)制數(shù)值等于2:

用戶三位圖如下十進(jìn)制數(shù)值等于1:

用戶四位圖如下十進(jìn)制數(shù)值等于7:

現(xiàn)在我們可以填寫數(shù)據(jù)表第三列:
| id | name | user_flag |
|---|---|---|
| 101 | 用戶一 | 4 |
| 102 | 用戶二 | 2 |
| 103 | 用戶三 | 1 |
| 104 | 用戶四 | 7 |
3.4 代碼實(shí)例
(1) 枚舉定義
定義枚舉時不要直接定義為1、2、4這類數(shù)字,而是采用位移方式進(jìn)行定義,這樣使用者可以明白設(shè)計(jì)者的意圖。
/**
?*?用戶角色枚舉
?*
?*?@author?微信公眾號「JAVA前線」
?*
?*/
public?enum?UserRoleEnum?{
????//?1?->?00000001
????NORMAL(1,?"普通用戶"),
????//?2?->?00000010
????MANAGER(1?<<?1,?"管理員"),
????//?4?->?00000100
????SUPER(1?<<?2,?"超級管理員")
????;
????private?int?code;
????private?String?description;
????private?UserRoleEnum(Integer?code,?String?description)?{
????????this.code?=?code;
????????this.description?=?description;
????}
????public?String?getDescription()?{
????????return?description;
????}
????public?int?getCode()?{
????????return?this.code;
????}
}
假設(shè)用戶已經(jīng)具有普通用戶角色,我們需要為其增加管理員角色,這就是新增角色,與之對應(yīng)還有刪除角色和查詢角色,這些操作需要用到為位運(yùn)算,詳見代碼注釋。
/**
?*?用戶角色枚舉
?*
?*?@author?微信公眾號「JAVA前線」
?*
?*/
public?enum?UserRoleEnum?{
????//?1?->?00000001
????NORMAL(1,?"普通用戶"),
????//?2?->?00000010
????MANAGER(1?<<?1,?"管理員"),
????//?4?->?00000100
????SUPER(1?<<?2,?"超級管理員")
????;
????//?新增角色?->?位或操作
????//?oldRole?->?00000001?->?普通用戶
????//?addRole?->?00000010?->?新增管理員
????//?newRole?->?00000011?->?普通用戶和管理員
????public?static?Integer?addRole(Integer?oldRole,?Integer?addRole)?{
????????return?oldRole?|?addRole;
????}
????//?刪除角色?->?位異或操作
????//?oldRole?->?00000011?->?普通用戶和管理員
????//?delRole?->?00000010?->?刪除管理員
????//?newRole?->?00000001?->?普通用戶
????public?static?Integer?removeRole(Integer?oldRole,?Integer?delRole)?{
????????return?oldRole?^?delRole;
????}
????//?是否有某種角色?->?位與操作
????//?allRole?->?00000011?->?普通用戶和管理員
????//?qryRole?->?00000001?->?是否有管理員角色
????//?resRole?->?00000001?->?有普通用戶角色
????public?static?boolean?hasRole(Integer?allRole,?Integer?qryRole)?{
????????return?qryRole?==?(role?&?qryRole);
????}
????private?int?code;
????private?String?description;
????private?UserRoleEnum(Integer?code,?String?description)?{
????????this.code?=?code;
????????this.description?=?description;
????}
????public?String?getDescription()?{
????????return?description;
????}
????public?int?getCode()?{
????????return?this.code;
????}
????public?static?void?main(String[]?args)?{
????????System.out.println(addRole(1,?2));
????????System.out.println(removeRole(3,?1));
????????System.out.println(hasRole(3,?1));
????}
}
(2) 數(shù)據(jù)查詢
假設(shè)在運(yùn)營后臺查詢界面中,需要查詢具有普通用戶角色的用戶數(shù)據(jù),可以使用如下SQL語句:
select?*?from?user_role?where?(user_flag?&?1)?=?user_flag;
select?*?from?user_role?where?(user_flag?&?b'0001')?=?user_flag;
我們也可以使用如下MyBatis語句:
<select?id="selectByUserRole"?resultMap="BaseResultMap"?parameterType="java.util.Map">
??select?*?from?user_role?
??where?user_flag?&?#{userFlag}?=?#{userFlag}
</select>
<select?id="selectByUserIdAndRole"?resultMap="BaseResultMap"?parameterType="java.util.Map">
??select?*?from?user_role?
??where?id?=?#{userId}?and?user_flag?&?#{userFlag}?=?#{userFlag}
</select>
4 文章總結(jié)
本文首先分析了位運(yùn)算在Java線程池源碼的應(yīng)用,然后我們又介紹了位圖法,這樣一個字段就可以表示多個含義,從而減少了字段冗余,節(jié)省了對接和開發(fā)的成本。當(dāng)然位圖法也有缺點(diǎn),例如數(shù)據(jù)庫字段含義不直觀需要進(jìn)行轉(zhuǎn)義,增加了代碼理解成本,大家可以根據(jù)需求場景選擇使用,希望本文對大家有所幫助。
--? End? --
———————
1.原創(chuàng)不易,你的「在看」是我創(chuàng)作的動力。
2.歡迎關(guān)注公眾號?牧小碼農(nóng),「帶你一起學(xué)Java」!
3.疫情期間,勤洗手,戴口罩,做好個人防護(hù)。
??????????
????????????????????
“在看轉(zhuǎn)發(fā)”是最大的支持
