写在前面
作者Doug Lea_如此描述这个类:An implementation of {@link java.util.concurrent.locks.ReadWriteLock} supporting similar semantics to {@link java.util.concurrent.locks.ReentrantLock}.
ReentratReadWriteLock是一个可重入的读写锁,实现了ReadWriteLock接口,具有与ReentrantLock同样的语义。此外,ReentrantReadWriteLock只支持_65535个可重入写锁和65535个读锁,以及其他一些特性,如下示例中的特性。
源码分析自 JDK 1.8.0_171
接着看一下使用示例:
1 | // 锁降级,write -> read |
以上示例就是ReentrantReadWriteLock的基础用法了。另外的一些用法即特性,如WriteLock也有newCondition的api,写锁降级,可重入,写锁线程能获取读锁但反过来却不行,等等特性。
内部变量
内部共有三个变量,实现 java.util.concurrent.locks.Lock
接口的 ReadLock
和 WriteLock
;以及继承自AQS的抽象类Sync实例, FairSync
和 NonfairSync
继承自Sync,表示该锁具备公平/非公平语义,有一个带boolean参数的构造函数,根据这个boolean参数决定sync为哪个子类实例。
在展开加解锁流程前,先看一下上述提起的内部类。
Sync
内部类Sync继承自AQS,主要功能是通过这个类提供:
1 | abstract static class Sync extends AbstractQueuedSynchronizer { |
同样,也是利用AQS的state变量来标识锁的个数,不同的是,ReentrantReadWriteLock利用高16位表示读锁,低16位表示写锁。两个抽象方法 writerShouldBlock
和 ReaderShouldBlock
,子类 FairSync
和 NonfairSync
通过实现这两个方法来提供公平/非公平锁的功能。
ReadLock
实现 java.util.concurrent.locks.Lock
接口:
1 | public static class ReadLock implements java.util.concurrent.locks.Lock, java.io.Serializable { |
ReadLock是不支持 newCondition
这一api的。
WriteLock
同样,WriteLock也是实现了java.util.concurrent.locks.Lock接口:
1 | public static class WriteLock implements java.util.concurrent.locks.Lock, java.io.Serializable { |
WriteLock是支持newCondition api的,这一api是构造一个AQS的内部类实例,具体可以看我之前的文章。
WriteLock.lock
先看一下写锁的加锁流程。WriteLock.lock()
调用内部类Sync的 acquire(1)
方法,acquire方法AQS提供的一个方法:
1 | // from AQS |
可见写锁加锁过程就是对state变量进行操作的过程,公平/非公平锁主要是通过 writerShouldBlock
这个方法,非公平这个方法直接返回false,也就是抢占式地去获取锁,而公平则是会查看队列里是否有前驱,如有则失败。
WriteLock.unlock
接着是写锁的解锁过程。
同样,也是调用Sync内部方法 release(1)
, release也为AQS的一个方法:
1 | public final boolean release(int arg) { |
ReadLock.lock
方法内部调用的是AQS的 acquireShared(1)
方法:
1 | public final void acquireShared(int arg) { |
结合Sync的解析,读锁的获取过程就是对state这一变量的操作过程。解析还是很清晰的,具体过程可以看一下解析。
ReadLock.unlock
unlock调用的仍是AQS内部方法, releaseShared(1)
:
1 | public final boolean releaseShared(int arg) { |
可见,解锁读锁,是对state操作,state-=_SHARED_UNIT,_接着再查看队列是否有等待节点,如有则需唤醒。
总结
- state内部变量的高16位表示所有线程持有读锁个数,低16位表示持有写锁个数
- 读写锁都为可重入的
- 写锁为互斥锁,同时持有线程持有读锁。加解锁过程均为cas操作state变量。加锁过程state+=1,解锁过程state-=1。加锁时如果其它线程持有锁,则往队列里添加一个EXCLUSIVE节点表示当前线程等待写锁,并在当前位置park住等待唤醒,解锁时,如果队列里有等待节点则需要唤醒节点对应线程
- 读锁为共享锁,多个线程能同时获得读锁。加锁过程即为state+=SHARED_UNIT(2^16)。加解锁过程是cas操作state变量高16位。加锁过程为state+=2^16,解锁过程state-=2^16。加锁时如果其它线程持有写锁,则往队列里添加一个SHARED节点标识当前线程等待读锁,并在当前位置park住等待唤醒,解锁时如果队列里有等待节点则需唤醒对应节点线程
- 能看到,无论Fari还是Nonfair,写锁的获取都有可能因为读锁阻塞,在一定情况下,造成了获取写锁的线程饥饿的现象