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的源码分析就完成了。