歡迎你們關注公衆號「JAVA前線」查看更多精彩分享文章,主要包括源碼分析、實際應用、架構思惟、職場分享、產品思考等等,同時歡迎你們加我我的微信「java_front」一塊兒交流學習java
0 文章概述
咱們閱讀ThreadPoolExecutor源碼時在開篇就會發現不少位運算代碼:數據庫
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; } }
不難發現線程狀態都用位運算表示,可是爲何要這樣作呢?爲何不定義爲直觀的數字呢?下面咱們進行分析。雖然代碼量很少,可是想要理解線程池就必需要理解爲何使用位運算。微信
ThreadPoolExecutor在設計時就是用一個int數值表示了兩個業務含義:線程池狀態和線程數量。其中高3位表示線程池狀態,低29位表示線程數量,這個設計思想體如今如下三句代碼:架構
// 代碼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位能夠表示線程數量,代碼3表示理論上最大線程數量爲536870911,這個理論值足以支撐線程池使用。源碼分析
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;
咱們知道COUNT_BITS=29則上述代碼等價於:this
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;
如今咱們算一算這些狀態等於多少,在這裏咱們從後往前算,由於RUNNING狀態是負數左移運算,計算步驟稍微多一些。spa
(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(原碼符號位不變、數值位取反) 補碼:11111111 11111111 11111111 11111111(反碼+1) 左移:11100000 00000000 00000000 00000000
2 位運算應用
runStateOf方法用來獲取線程池狀態信息,workerCountOf方法用來獲取線程池線程數,ctlOf方法用來設置當前線程池狀態和線程數量信息,咱們分別進行計算。線程
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
如今一個線程池狀態是RUNNING而且線程數量等於3用二進制表示以下:設計
11100000 00000000 00000000 00000011
執行runStateOf方法就能夠獲得線程池狀態:
11100000 00000000 00000000 00000011 & 11100000 00000000 00000000 00000000 = 11100000 00000000 00000000 00000000
(4) workerCountOf
如今一個線程池狀態是RUNNING而且線程數量等於4用二進制表示以下:
11100000 00000000 00000000 00000100
執行workerCountOf方法就能夠獲得線程數量:
11100000 00000000 00000000 00000100 & 00011111 11111111 11111111 11111111 = 00000000 00000000 00000000 00000100
(5) ctlOf
如今咱們要設置一個狀態是RUNNING且線程數量等於4的線程池ctl值:
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 4)); 11100000 00000000 00000000 00000000 | 00000000 00000000 00000000 00000100 = 11100000 00000000 00000000 00000100
這個方法真正體現了高3位表示線程池狀態,低29位表示線程數量這個設計思想優勢,本來須要兩步設置動做如今只須要一步,從而實現了操做原子性,這樣就能夠知足線程池的不少CAS操做,例如線程池在調用addWorker新增工做線程數時會調用compareAndIncrementWorkerCount方法增長線程數量。
可是假設同一時刻shutdownNow方法致使線程池狀態發生改變,那麼新增工做線程數方法就不會調用成功,須要繼續執行自旋進行嘗試,這體現了線程狀態和線程數量維護的原子性。
3 位圖法應用
3.1 需求背景
咱們看看位運算怎樣應用在實際開發場景。假設在系統中用戶一共有三種角色:普通用戶、管理員、超級管理員,如今須要設計一張用戶角色表記錄這類信息。咱們不難設計出以下方案:
![線程池源碼解析系列:爲何要使用位運算表示線程池狀態](http://static.javashuo.com/static/loading.gif)
咱們使用1表示是,0表示否,那麼觀察上表不可貴出,用戶一有用超級管理員角色,用戶二具備管理員角色,用戶三具備普通用戶角色,用戶四同時具備三種角色。若是此時新增長一種角色呢?那麼新增一個字段便可。
3.2 發現問題
按照上述一個字段表示一種角色進行表設計功能上是沒有問題的,優勢是容易理解結構清晰,可是咱們想想有沒有什麼問題?筆者遇到過以下問題:在複雜業務環境一份數據可能會使用在不一樣的場景,例如上述數據存儲在MySQL數據庫,這一份數據還會被用在以下場景:
檢索數據須要同步一份到ES
業務方使用此表經過Flink計算業務指標
業務方訂閱此表Binlog消息進行業務處理
若是表結構發生變化,數據源之間就要從新進行對接,業務方也要進行代碼修改,這樣開發成本比較很是高。有沒有辦法避免此類問題?
3.3 解決方案
咱們可使用位圖法,這樣同一個字段能夠表示多個業務含義。首先設計以下數據表,userFlag字段暫時不填。
![線程池源碼解析系列:爲何要使用位運算表示線程池狀態](http://static.javashuo.com/static/loading.gif)
咱們設計位圖每個bit表示一種角色:
![線程池源碼解析系列:爲何要使用位運算表示線程池狀態](http://static.javashuo.com/static/loading.gif)
咱們使用位圖法表示以下數據表:
![線程池源碼解析系列:爲何要使用位運算表示線程池狀態](http://static.javashuo.com/static/loading.gif)
用戶一位圖以下十進制數值等於4:
![線程池源碼解析系列:爲何要使用位運算表示線程池狀態](http://static.javashuo.com/static/loading.gif)
用戶二位圖以下十進制數值等於2:
![線程池源碼解析系列:爲何要使用位運算表示線程池狀態](http://static.javashuo.com/static/loading.gif)
用戶三位圖以下十進制數值等於1:
![線程池源碼解析系列:爲何要使用位運算表示線程池狀態](http://static.javashuo.com/static/loading.gif)
用戶四位圖以下十進制數值等於7:
![線程池源碼解析系列:爲何要使用位運算表示線程池狀態](http://static.javashuo.com/static/loading.gif)
如今咱們能夠填寫數據表第三列:
![線程池源碼解析系列:爲何要使用位運算表示線程池狀態](http://static.javashuo.com/static/loading.gif)
3.4 代碼實例
(1) 枚舉定義
定義枚舉時不要直接定義爲一、二、4這類數字,而是採用位移方式進行定義,這樣使用者能夠明白設計者的意圖。
/** * 用戶角色枚舉 * * @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; } }
假設用戶已經具備普通用戶角色,咱們須要爲其增長管理員角色,這就是新增角色,與之對應還有刪除角色和查詢角色,這些操做須要用到爲位運算,詳見代碼註釋。
/** * 用戶角色枚舉 * * @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) 數據查詢
假設在運營後臺查詢界面中,須要查詢具備普通用戶角色的用戶數據,可使用以下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 文章總結
本文首先分析了位運算在Java線程池源碼的應用,而後咱們又介紹了位圖法,這樣一個字段就能夠表示多個含義,從而減小了字段冗餘,節省了對接和開發的成本。固然位圖法也有缺點,例如數據庫字段含義不直觀須要進行轉義,增長了代碼理解成本,你們能夠根據需求場景選擇使用,但願本文對你們有所幫助。
歡迎你們關注公衆號「JAVA前線」查看更多精彩分享文章,主要包括源碼分析、實際應用、架構思惟、職場分享、產品思考等等,同時歡迎你們加我我的微信「java_front」一塊兒交流學習