圖文并茂的聊聊ReentrantReadWriteLock的位運算
我相信不少讀者,在看JDK源碼時,會看到位運算代碼,可能有些人和阿星一樣是轉(zhuǎn)行的,缺乏計算機(jī)相關(guān)的基礎(chǔ)知識,看的是一頭霧水。

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

int占4個字節(jié),一個字節(jié)8位,總共32位,切割一下,高16位表示讀,低16位表示寫。
這樣做的好處就是節(jié)約資源,就像現(xiàn)實中老板把你一個人當(dāng)兩個人用是一樣的道理。
講到這里,大家也明白了,上面的位運算代碼就是完成高低位切割的。
讀鎖位運算
//偏移位數(shù)
static final int SHARED_SHIFT = 16;
//讀鎖計數(shù)基本單位
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
讀鎖使用高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 << SHARED_SHIFT) - 1;
//獲取寫鎖重入數(shù)
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
剩下的寫鎖就非常簡單,獲取低16位不用左右移動,只要把高16位全部補(bǔ)0即可。

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

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

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

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

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

現(xiàn)在有感覺了吧,c的高16位都會變成0,低16位會原樣保留,最終達(dá)到獲取低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 < buf.length; 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
如果相對應(yīng)位都是 0,則結(jié)果為 0,否則為 1
0xff的二進(jìn)制是11111111,0xff后面每追加2個0,效果等于左移8位,依次類推,所以我們最終是利用<<、|、&、0xff來還原。
過程圖如下

小結(jié)
在ReentrantReadWriteLock中讀寫狀態(tài)公用一個狀態(tài),巧妙的利用高低位來節(jié)約資源,在整個實現(xiàn)過程中,使用了位運算來做高低位切割。
2. 國產(chǎn)最強(qiáng)開源 API 網(wǎng)關(guān),沒有之一,不接受任何反駁!
最近面試BAT,整理一份面試資料《Java面試BATJ通關(guān)手冊》,覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫、數(shù)據(jù)結(jié)構(gòu)等等。
獲取方式:點“在看”,關(guān)注公眾號并回復(fù) Java 領(lǐng)取,更多內(nèi)容陸續(xù)奉上。
文章有幫助的話,在看,轉(zhuǎn)發(fā)吧。
謝謝支持喲 (*^__^*)

