图文并茂的聊聊 ReentrantReadWriteLock 的位运算
大家好,我是阿星,欢迎来到Java
并发编程系列第六篇ReentrantReadWriteLock
基础,今天我们来聊一聊读写状态的设计。
我相信不少读者,在看JDK
源码时,会看到位运算代码,可能有些人和阿星一样是转行的,缺乏计算机相关的基础知识,看的是一头雾水。
导致有些人直接被劝退,也有些人选择理解字面上的意思,细节跳过。
但是一颗疑惑的种子在我们心中埋了下来「为什么使用位运算就能达到这样的效果?」。
恰好ReentrantReadWriteLock
读写状态的设计用到了位运算,我们以此来展开今天的话题。
一段位运算代码
我们来到ReentrantReadWriteLock.Sync
内部类,发现了这段代码(后面以RRW
简称)
上面的这些位运算代码是用来干嘛的?
因为RRW
中的int
整型变量state
要同时维护读锁、写锁两种状态,所以RRW
的是通过高低位切割来实现。
int
占4
个字节,一个字节8
位,总共32
位,切割一下,高16
位表示读,低16
位表示写。
这样做的好处就是节约资源,就像现实中老板把你一个人当两个人用是一样的道理。
讲到这里,大家也明白了,上面的位运算代码就是完成高低位切割的。
读锁位运算
读锁使用高16
位,每次获取读锁成功+1
,所以读锁计数基本单位是1
的高16
位,即1
左移16
位(1 << 16
)。
1
左移16
位等于65536
,每次获取读锁成功都+65536
,这时有读者跳出来问,不是+1
嘛,怎么变成+65536
了,这不对啊。
别急别急,看看下面这段代码
上面sharedCount
函数通过位运算是做无符号右移16
位获取读锁的重入数,为什么可以获取到呢?
阿星原地向前走16
步,再后退16
步,又回到原点,1
左移16
位等于65536
,65536
右移16
位等于1
。
比如我们获取到了3
次读锁,就是65536 * 3 = 196608
,转换下公式就是3
左移16
位等于196608
,196608
右移16
位等于3
。
虽然我们每次获取到读锁都会+65536
,但是获取读锁时会做右移16
位,所以效果和+1
是一样。
写锁位运算
剩下的写锁就非常简单,获取低16
位不用左右移动,只要把高16
位全部补0
即可。
反推一下,因为不需要左右移动,其实就和正常的数字一样,只不过因为高16
位补0
,导致数值范围在0~65535
,也就是说写锁获取成功直接+1
就好了。
我们目光转到EXCLUSIVE_MASK
变量,1
右移16
位后-1
,得到65535
,65535
的二进制就是111111111111111
。
现在来看exclusiveCount
函数,该函数内做了位运算&
,&
又称"与"运算。
"与"运算是两个二进制,每位数运算,运算规则如下
0&0=0
0&1=0
1&0=0
1&1=1
如果相对应位都是 1,则结果为 1,否则为 0
可能有些读者大大还是不太明白,下面放张图16
位二进制"与"运算图
我们发现"与"运算时,只要有一方为0
,那结果一定是0
,所以为了切割低16
位,可以使用&
来完成。
从上图可以看出,EXCLUSIVE_MASK
高16
位都是0
,低16
位都是1
,和它&
的变量,高16
位全部会变成0
,低16
位全部保留下来,最终达到获取低16
位效果。
c & EXCLUSIVE_MASK
,假设c
是1
,&
的过程如下图
这样看可能没太大感觉,我们把数值调大点,假设c
是65536
和65537
,&
的过程如下图
现在有感觉了吧,c
的高16
位都会变成0
,低16
位会原样保留,最终达到获取低16
位效果。
EXCLUSIVE_MASK
范围在0~65535
,所以c
的范围也不会超过0~65535
,因为超过了也会通过& EXCLUSIVE_MASK
回到0~65535
。
提个问题
「阿星」:int
如何实现序列化与反序列化?
「萌新」:好家伙,我直接用Integer
就好了,父类Number
实现了序列化接口Serializable
。
「阿星」:不使用Serializable
,自己手写一个呢?
「萌新」:啊,这。。。。
为了让大家更好的消化之前的内容,阿星手把手带大家实现int
与字节的互转。
int
占4
个字节,一个字节8
位,总共32
位。
int 转 byte 数组
思路很简单,我们只需要从右往左按8
位一个一个截取,再存储到byte
数组里面,代码如下:
过程图如下
byte 数组转 int
我们从int
转换成了byte[]
,现在要从byte[]
转换成int
,代码如下
代码中涉及到了"左移、与、或"位运算,左移和与我们前面都说了,还有一个或,或和与一样,只是运算规则不同,或的运算规则如下
0|0=0
0|1=1
1|0=1
1|1=1
如果相对应位都是 0,则结果为 0,否则为 1
0xff
的二进制是11111111
,0xff
后面每追加2
个0
,效果等于左移8
位,依次类推,所以我们最终是利用<<、|、&、0xff
来还原。
过程图如下
小结
在ReentrantReadWriteLock
中读写状态公用一个状态,巧妙的利用高低位来节约资源,在整个实现过程中,使用了位运算来做高低位切割。
历史好文推荐
福利
关于我
这里是阿星,一个热爱技术的 Java 程序猿,公众号 「程序猿阿星」 里将会定期分享操作系统、计算机网络、Java、分布式、数据库等精品原创文章,2021,与您在 Be Better 的路上共同成长!。
版权声明: 本文为 InfoQ 作者【程序猿阿星】的原创文章。
原文链接:【http://xie.infoq.cn/article/4722ace126e3cb98f9a8f3e1c】。文章转载请联系作者。
评论