1. 阅读须知
JDK版本:1.8
文章中使用/**/注释的方法会做深入分析
2. 正文
ReentrantReadWriteLock
,从字面上理解为可重入读写锁,基于AQS
(AbstractQueuedSynchronizer
,不了解AQS的读者可以去看笔者关于AQS源码解析的文章进行学习)实现,根据读写锁的特性,我们可以猜测,读锁应该是基于AQS的共享锁实现,而写锁应该是基于AQS的独占锁实现,我们来验证这个猜想,首先看一下ReentrantReadWriteLock
的构造方法:
ReentrantReadWriteLock:
1 | public ReentrantReadWriteLock(boolean fair) { |
ReentrantReadWriteLock
默认的无参构造方法使用的是非公平锁。我们来介绍一下ReentrantReadWriteLock
中的同步器Sync
中的一些变量:
ReentrantReadWriteLock.Sync:
1 | //读锁占用高16位表示持有读锁的线程的数量 |
ReentrantReadWriteLock
使用AQS
的state
的高16位表示持有读锁的线程的数量,低16位表示写锁被同一个线程申请的次数,也就是锁重入的次数。接下来我们来看加锁实现,我们首先来看读锁部分:
ReentrantReadWriteLock.ReadLock:
1 | public void lock() { |
acquireShared
方法我们在AQS
(AbstractQueuedSynchronizer
)源码解析(共享锁部分)这篇文章中进行过详细分析,方法的开始会调用有子类实现的tryAcquireShared
方法尝试以共享模式获得锁,我们来看ReentrantReadWriteLock
对tryAcquireShared
方法的实现:
ReentrantReadWriteLock.Sync:
1 | protected final int tryAcquireShared(int unused) { |
这里说明一下方法的执行过程:
- 如果另一个线程持有写锁,则获取共享锁失败。
- 否则,此线程符合锁定状态,判断是否应该因为队列策略而阻塞。如果没有,尝试通过CAS增加共享锁的持有线程的数量。请注意,这步不会检查重入获取,它会被推迟到
fullTryAcquireShared
方法执行,以避免在更典型的非重入情况下检查锁重入计数。
- 否则,此线程符合锁定状态,判断是否应该因为队列策略而阻塞。如果没有,尝试通过CAS增加共享锁的持有线程的数量。请注意,这步不会检查重入获取,它会被推迟到
- 如果步骤2因线程需要阻塞或CAS失败或计数饱和而失败,则调用
fullTryAcquireShared
方法。 关于readerShouldBlock
(判断当前获取读锁的线程是否需要阻塞)方法,有公平和非公平两种实现,我们首先来看非公平的实现:
- 如果步骤2因线程需要阻塞或CAS失败或计数饱和而失败,则调用
ReentrantReadWriteLock.NonfairSync:
1 | final boolean readerShouldBlock() { |
apparentlyFirstQueuedIsExclusive
方法来自AQS,主要用于判断等待队列的头结点的下一个节点也就是第一个排队的线程是否以独占模式等待。这里我们要结合调用readerShouldBlock
方法之前的if判断进行分析,如果这个if判断不满足,说明有两种情况可能发生:
- 当前没有线程占用写锁,这种情况readerShouldBlock方法会返回false。
- 当前有线程占用写锁,并且占用写锁的线程就是当前线程(当前线程是
head
节点),这时就发生了锁降级的情况,也就是当前线程持有写锁,并在申请读锁,这时就要判断head
节点的下一个节点是否要申请写锁,如果是则readerShouldBlock
方法返回true
,说明本次申请读锁的操作需要阻塞。
- 当前有线程占用写锁,并且占用写锁的线程就是当前线程(当前线程是
接下来我们来看readerShouldBlock
方法公平锁的实现:
ReentrantReadWriteLock.FairSync:
1 | final boolean readerShouldBlock() { |
hasQueuedPredecessors
方法我们在ReentrantLock
源码解析这篇文章中分析过,它的主要作用是确认当前线程是否是下一个能够优先获得锁的线程,公平性也就是通过这个判断来保证的。公平锁我们很好理解,就是根据等待队列中节点的顺序来保证获取锁的顺序。
ReentrantReadWriteLock.Sync:
1 | final int fullTryAcquireShared(Thread current) { |
接下来我们来看读锁释放的实现:
ReentrantReadWriteLock.ReadLock:
1 | public void unlock() { |
这里的releaseShared
释放共享锁方法同样来自于AQS
,方法中首先会调用由子类覆盖的tryReleaseShared
方法,通过尝试设置state
变量来释放共享锁,我们来看ReentrantReadWriteLock
对于tryReleaseShared
方法的实现:
ReentrantReadWriteLock.Sync:
1 | protected final boolean tryReleaseShared(int unused) { |
到这里,读锁的加锁和解锁操作就分析完了,下面我们来分析写锁的加锁和解锁操作,首先来看加锁:
ReentrantReadWriteLock.WriteLock:
1 | public void lock() { |
果然,写锁是基于AQS
的独占锁实现,这里的acquire
方法我们在AQS
(AbstractQueuedSynchronizer
)源码解析(独占锁部分)这篇文章中已经详细分析过,方法的第一步就是调用由子类实现的tryAcquire
方法通过操作state
变量尝试以独占模式获取锁,我们来看ReentrantReadWriteLock
对tryAcquire
方法的实现:
ReentrantReadWriteLock.Sync:
1 | protected final boolean tryAcquire(int acquires) { |
这里的writerShouldBlock
方法同样区分公平和非公平两个版本的实现,我们先来看非公平版本的实现:
ReentrantReadWriteLock.NonfairSync:
1 | final boolean writerShouldBlock() { |
我们发现方法直接返回false
,也就是说每个想要获取非公平写锁的线程都可以直接参与竞争。而writerShouldBlock
方法公平锁的版本与读锁的readerShouldBlock
方法的公平版本是一样的,都是需要确认当前线程是否是下一个能够优先获得锁的线程,以此来保证公平性。最后我们来看写锁的解锁操作:
ReentrantReadWriteLock.WriteLock:
1 | public void unlock() { |
这里的release
方法我们在AQS
独占锁源码解析的文章中同样进行过详细的分析,AQS
的release
方法首先会尝试调用由子类实现的tryRelease
方法来尝试设置state
变量来释放独占锁,锁完全释放后,会对后继节点进行唤醒操作,这个流程我们已经分析过,不再赘述。我们来看ReentrantReadWriteLock
的对tryRelease
方法的实现:
ReentrantReadWriteLock.WriteLock:
1 | protected final boolean tryRelease(int releases) { |
这样解锁的流程就分析完成了。
ReentrantReadWriteLock
的写锁还支持Condition
,与ReentrantLock
一样完全基于AQS
的ConditionObject
实现,我们已经分析过ConditionObject
源码,不明白的同学可以前往进行查阅学习。到这里,ReentrantReadWriteLock
的源码分析就完成了。