圖文并茂的聊聊ReentrantReadWriteLock的位運算
大家好,歡迎來到學習ReentrantReadWriteLock基礎,今天我們來聊一聊讀寫狀態(tài)的設計。
我相信不少讀者,在看JDK源碼時,會看到位運算代碼,可能有些人和阿星一樣是轉(zhuǎn)行的,缺乏計算機相關(guān)的基礎知識,看的是一頭霧水。

導致有些人直接被勸退,也有些人選擇理解字面上的意思,細節(jié)跳過。
但是一顆疑惑的種子在我們心中埋了下來「為什么使用位運算就能達到這樣的效果?」。
恰好ReentrantReadWriteLock讀寫狀態(tài)的設計用到了位運算,我們以此來展開今天的話題。
一段位運算代碼
我們來到ReentrantReadWriteLock.Sync內(nèi)部類,發(fā)現(xiàn)了這段代碼(后面以RRW簡稱)
????????
//偏移位數(shù)
static?final?int?SHARED_SHIFT?=?16;
//讀鎖計數(shù)基本單位
static?final?int?SHARED_UNIT?=?(1?<//讀鎖、寫鎖可重入最大數(shù)量
static?final?int?MAX_COUNT?=?(1?<1;
//獲取低16位的條件
static?final?int?EXCLUSIVE_MASK?=?(1?<1;
//獲取讀鎖重入數(shù)
static?int?sharedCount(int?c)????{?return?c?>>>?SHARED_SHIFT;?}
//獲取寫鎖重入數(shù)
static?int?exclusiveCount(int?c)?{?return?c?&?EXCLUSIVE_MASK;?}
上面的這些位運算代碼是用來干嘛的?
因為RRW的中的int整型變量state要同時維護讀鎖、寫鎖兩種狀態(tài),所以RRW的是通過高低位切割來實現(xiàn)。

int占4個字節(jié),一個字節(jié)8位,總共32位,切割一下,高16位表示讀,低16位表示寫。
這樣做的好處就是節(jié)約資源,就像現(xiàn)實中老板把你一個人當兩個人用是一樣的道理。
講到這里,大家也明白了,上面的位運算代碼就是完成高低位切割的。
讀鎖位運算
//偏移位數(shù)
static?final?int?SHARED_SHIFT?=?16;
//讀鎖計數(shù)基本單位
static?final?int?SHARED_UNIT?=?(1?<讀鎖使用高16位,每次獲取讀鎖成功+1,所以讀鎖計數(shù)基本單位是1的高16位,即1左移16位(1 << 16)。

1左移16位等于65536,每次獲取讀鎖成功都+65536,這時有讀者跳出來問,不是+1嘛,怎么變成+65536了,這不對啊。
別急別急,看看下面這段代碼
//偏移位數(shù)
static?final?int?SHARED_SHIFT?=?16;
//獲取讀鎖重入數(shù)
static?int?sharedCount(int?c)????{?return?c?>>>?SHARED_SHIFT;?}
上面sharedCount函數(shù)通過位運算是做無符號右移16位獲取讀鎖的重入數(shù),為什么可以獲取到呢?

阿星原地向前走16步,再后退16步,又回到原點,1左移16位等于65536,65536右移16位等于1。
比如我們獲取到了3次讀鎖,就是65536 * 3 = 196608,轉(zhuǎn)換下公式就是3左移16位等于196608,196608右移16位等于3。
雖然我們每次獲取到讀鎖都會+65536,但是獲取讀鎖時會做右移16位,所以效果和+1是一樣。
寫鎖位運算
//偏移位數(shù)
static?final?int?SHARED_SHIFT?=?16;
//獲取低16位的條件
static?final?int?EXCLUSIVE_MASK?=?(1?<1;
//獲取寫鎖重入數(shù)
static?int?exclusiveCount(int?c)?{?return?c?&?EXCLUSIVE_MASK;?}
剩下的寫鎖就非常簡單,獲取低16位不用左右移動,只要把高16位全部補0即可。

反推一下,因為不需要左右移動,其實就和正常的數(shù)字一樣,只不過因為高16位補0,導致數(shù)值范圍在0~65535,也就是說寫鎖獲取成功直接+1就好了。

我們目光轉(zhuǎn)到EXCLUSIVE_MASK變量,1右移16位后-1,得到65535,65535的二進制就是111111111111111。
現(xiàn)在來看exclusiveCount函數(shù),該函數(shù)內(nèi)做了位運算&,&又稱"與"運算。
"與"運算是兩個二進制,每位數(shù)運算,運算規(guī)則如下
0&0=00&1=01&0=01&1=1
如果相對應位都是1,則結(jié)果為1,否則為0
可能有些讀者大大還是不太明白,下面放張圖16位二進制"與"運算圖

我們發(fā)現(xiàn)"與"運算時,只要有一方為0,那結(jié)果一定是0,所以為了切割低16位,可以使用&來完成。

從上圖可以看出,EXCLUSIVE_MASK高16位都是0,低16位都是1,和它&的變量,高16位全部會變成0,低16位全部保留下來,最終達到獲取低16位效果。
c & EXCLUSIVE_MASK,假設c是1,&的過程如下圖

這樣看可能沒太大感覺,我們把數(shù)值調(diào)大點,假設c是65536和65537,&的過程如下圖

現(xiàn)在有感覺了吧,c的高16位都會變成0,低16位會原樣保留,最終達到獲取低16位效果。
EXCLUSIVE_MASK范圍在0~65535,所以c的范圍也不會超過0~65535,因為超過了也會通過& EXCLUSIVE_MASK回到0~65535。
提個問題
「阿星」:int如何實現(xiàn)序列化與反序列化?
「萌新」:好家伙,我直接用Integer就好了,父類Number實現(xiàn)了序列化接口Serializable。
「阿星」:不使用Serializable,自己手寫一個呢?
「萌新」:啊,這。。。。
為了讓大家更好的消化之前的內(nèi)容,阿星手把手帶大家實現(xiàn)int與字節(jié)的互轉(zhuǎn)。

int占4個字節(jié),一個字節(jié)8位,總共32位。
int轉(zhuǎn)byte數(shù)組
思路很簡單,我們只需要從右往左按8位一個一個截取,再存儲到byte數(shù)組里面,代碼如下:
public?static?byte[]?intToBytes(int?n)?{
????????//長度4字節(jié)的數(shù)組
????????byte[]?buf?=?new?byte[4];
????????for?(int?i?=?0;?i?????????????//循環(huán)右移動8位,存儲到數(shù)組中
????????????buf[i]?=?(byte)?(n?>>?(8?*?i));
????????}
????????return?buf;
????}
過程圖如下

byte數(shù)組轉(zhuǎn)int
我們從int轉(zhuǎn)換成了byte[],現(xiàn)在要從byte[]轉(zhuǎn)換成int,代碼如下
public?static?int?bytesToInt(byte[]?buf)?{
????return?buf[0]?&?0xff
????????????|?((buf[1]?<8)?&?0xff00)
????????????|?((buf[2]?<16)?&?0xff0000)
????????????|?((buf[3]?<24)?&?0xff000000);
}
代碼中涉及到了"左移、與、或"位運算,左移和與我們前面都說了,還有一個或,或和與一樣,只是運算規(guī)則不同,或的運算規(guī)則如下
0&0=00&1=11&0=11&1=1
如果相對應位都是 0,則結(jié)果為 0,否則為 1
0xff的二進制是11111111,0xff后面每追加2個0,效果等于左移8位,依次類推,所以我們最終是利用<<、|、&、0xff來還原。
過程圖如下

小結(jié)
在ReentrantReadWriteLock中讀寫狀態(tài)公用一個狀態(tài),巧妙的利用高低位來節(jié)約資源,在整個實現(xiàn)過程中,使用了位運算來做高低位切割。
