ReentrantReadWriteLock源码

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/ywlmsm1224811/article/details/91802016

       之前提到的锁(如 Metux 和 ReentrantLock )基本都是排他锁,这些锁在同一时刻只允许一个线程访问,而读写锁(ReentrantReadWriteLock)在同一时刻允许多个读线程访问,但是在写线程访问时,所有的读线程和写线程都会被阻塞。读写锁维护了一对锁,一个读锁和一个写锁,通过分离读锁和写锁,使得并发性相比一般的排他锁有了很大提升。多个读锁不互斥,读锁和写锁互斥,这是由 JVM 自己控制的,你只要上好锁就可以了。

       读写锁的性能都会比排他锁好,因为在大多数场景下读多于写,在读多于写的情况下,读写锁比排他锁有更好的并发性和吞吐量,并发包 读写锁的实现类是 ReentrantReadWriteLock,它的特性如下:

1)公平性:支持非公平(默认方式)和公平的锁获取方式,吞吐量还是非公平优于公平

2)重入性:该锁支持重入,以读写线程为例:读线程获取了读锁之后,能够再次获取读锁。而写线程获取了写锁之后能够再次获取写锁,同时也可以获取读锁(锁降级)

3)锁降级:遵循获取写锁、获取读锁再释放写锁的顺序,写锁能够降级为读锁

4)ReadLock 可以被多个线程持有并且在持有时排斥任何的 WriteLock,而 WriteLock 则是完全的互斥。这一特性非常重要,对于高频率读取而相对低频率写入的场景,使用此锁可以提高并发量

5)不管是 ReadLock 还是 WriteLock 都支持 interrupt,语义和 ReentrantLock 一致

6)WriteLock 支持 Condition 并且与 ReentrantLock 语义一致,而 ReadLock 不能使用 Condition,否则抛出 UnSupportedOperationException 异常

线程进入读锁的条件:    

(1)没有其他线程的写锁       

(2)没有写请求或者有写请求(调用线程和持有锁的线程是同一个)

线程进入写锁的条件:   

(1)没有其他线程的写锁       

(2)没有其他线程的读锁

读写锁的接口与示例:

       读写锁 ReadWriteLock 接口仅仅定义了 readLock 和 writeLock 两个方法,而其实现类 ReentrantReadWriteLock 除了实现两个接口方法外,还提供了一些便于外界监控其内部工作状态的方法,这些方法如下:

接下来我们写一个使用读写锁的demo,具体代码如下:

下面我们使用读写锁实现一个非线程安全的 HashMap 的缓存,代码如下:

读写锁的实现分析:

       接下来我们分析 ReentrantReadWriteLock 的实现,主要包括:读写状态的设计、写锁的获取与释放、读锁的获取与释放以及锁降级。

一、读写状态的实现

       ReentrantReadWriteLock 同样依赖于自定义同步器实现同步功能,而读写状态就是其同步器的同步状态。回想 ReentrantLock 中自定义同步器的实现,同步状态表示锁被一个线程重复获取的次数,而读写锁的自定义同步器需要在同步状态(一个整型变量)上维护多个读线程和一个写线程的状态,使得该状态的设计成为读写锁实现的关键。

       如果在一个整型变量上维护多种状态,就一定要“按位切割使用”这个变量,读写锁将变量切分成两个部分,高16位表示读,低16位表示写,划分方式如下图所示:

假设当前同步状态值为 S

写状态: S & 0X0000FFFF(将高16位全部抹去,即 左边16个0,右边16个1),写状态增加 1 时,等于 S +1

读状态: S >> 16(无符号补0,右移16位,只剩下高位16位),读状态增加 1 时,等于 S +(1 <<16),也就是 S + 0X00010000

       根据状态的划分得出一个推论:S 不等于 0 时,当写状态(S & 0X0000FFFF)等于0,而读状态(S >> 16)不等于 0 时,此时是读锁已被获取

二、写锁的获取与释放(独占锁)

(1)写锁的获取源代码如下:

(2)写锁的释放源代码如下:

三、读锁的获取于释放

(1)读锁的获取源代码如下:

(2)读锁的释放源码如下:

四、锁降级

锁降级指的是写锁降级为读锁。锁降级是指当前拥有写锁(未释放),再获取到读锁,随后释放写锁的过程。下面我们来看一个例子:

猜你喜欢

转载自blog.csdn.net/ywlmsm1224811/article/details/91802016