深入理解 ReentrantReadWriteLock 源码:基于 AQS 的读写锁实现
在多线程并发编程中读写锁ReadWriteLock是一种重要的同步工具它允许多个读线程同时访问共享资源但写线程必须独占访问。JDK 提供了ReentrantReadWriteLock它基于 AQSAbstractQueuedSynchronizer实现支持可重入、公平/非公平模式并巧妙地利用一个int型state变量同时维护读锁和写锁的计数。本文将深入剖析其核心源码帮助读者透彻理解读写锁的设计精髓。一、整体架构设计ReentrantReadWriteLock实现了ReadWriteLock接口对外暴露readLock()和writeLock()两个方法。其内部结构可以用“一个同步器 两把门面锁”来概括Sync继承 AQS 的抽象同步器承载所有同步逻辑如获取/释放、公平策略、读写计数。NonfairSync / FairSync实现具体的获取规则非公平或公平。ReadLock / WriteLock实现Lock接口的门面类将对锁的操作lock()、unlock()委托给同一个Sync实例。这种设计使得读锁和写锁共享同一个等待队列且读写互斥、写写互斥、读读共享的控制都集中在Sync中避免了状态分散。二、核心字段与构造方法我们直接给出ReentrantReadWriteLock的完整源码框架重点关注其字段和构造方法的设计public class ReentrantReadWriteLock implements ReadWriteLock {// 两把锁引用构造方法内实例化private final ReadLock readerLock;private final WriteLock writerLock;// 唯一同步器Sync是AQS子类final Sync sync;// 默认非公平构造public ReentrantReadWriteLock() {this(false);}public ReentrantReadWriteLock(boolean fair) {sync fair ? new FairSync() : new NonfairSync();// 提前创建读写锁对象传入自身this引用readerLock new ReadLock(this);writerLock new WriteLock(this);}// getter方法直接返回已经new好的对象不会临时创建Overridepublic Lock readLock() {return readerLock;}Overridepublic Lock writeLock() {return writerLock;}// ... 内部类 Sync、NonfairSync、FairSync、ReadLock、WriteLock 将在下文展开}设计亮点sync字段是final的在构造时一次性确定为公平或非公平同步器后续不可变保证了线程安全。readerLock和writerLock提前创建避免了readLock()/writeLock()方法中的临时对象创建开销且锁对象本身不持有可变状态所有状态都在sync中。三、同步器顶层抽象SyncSync是整个读写锁的核心它继承AbstractQueuedSynchronizer。读锁和写锁的计数均用一个int型state表示高 16 位存放读锁持有计数低 16 位存放写锁重入计数。abstract static class Sync extends AbstractQueuedSynchronizer {private static final long serialVersionUID 6317671515683782265L;// 移位常量state高低16位拆分static final int SHARED_SHIFT 16;static final int SHARED_UNIT (1 SHARED_SHIFT);static final int MAX_COUNT (1 SHARED_SHIFT) - 1;static final int EXCLUSIVE_MASK (1 SHARED_SHIFT) - 1;// 获取读锁计数高16位static int sharedCount(int c) { return c SHARED_SHIFT; }// 获取写锁计数低16位static int exclusiveCount(int c) { return c EXCLUSIVE_MASK; }// 写锁独占获取protected abstract boolean tryAcquire(int acquires);// 读锁共享获取protected abstract int tryAcquireShared(int acquires);// 写锁释放protected abstract boolean tryRelease(int releases);// 读锁释放protected abstract boolean tryReleaseShared(int releases);}关键解析sharedCount(int c)无符号右移 16 位得到当前所有线程持有的读锁次数每个线程可多次重入总体计数。exclusiveCount(int c)与掩码0x0000FFFF做与运算得到写锁重入次数。SHARED_UNIT为1 16即读锁每增加一次计数state加0x10000这样高 16 位1。MAX_COUNT为0x0000FFFF65535表示读锁和写锁的最大重入次数限制。四个抽象方法留给子类公平/非公平实现体现了模板方法模式。四、非公平同步器NonfairSync非公平模式是ReentrantReadWriteLock的默认策略它允许新来的线程“插队”以减少上下文切换开销。以下是NonfairSync的核心实现static final class NonfairSync extends Sync {private static final long serialVersionUID -8159625535654395037L;Overrideprotected boolean tryAcquire(int acquires) {// 写锁获取实现int state getState();int w exclusiveCount(state);if (w ! 0) {// 已经持有写锁判断是否当前线程可重入if (getExclusiveOwnerThread() Thread.currentThread()) {setState(state acquires);return true;}return false;}// 当前无写锁尝试CAS抢占写锁if (sharedCount(state) ! 0 || !compareAndSetState(state, state acquires)) {return false;}setExclusiveOwnerThread(Thread.currentThread());return true;}Overrideprotected int tryAcquireShared(int unused) {// 读锁获取核心逻辑int state getState();// 存在其他线程持有写锁读锁直接失败if (exclusiveCount(state) ! 0 getExclusiveOwnerThread() ! Thread.currentThread()) {return -1;}if (!readerShouldBlock() compareAndSetState(state, state SHARED_UNIT)) {return 1;}return fullTryAcquireShared(state);}// 判断新来读锁是否应当阻塞防止写饥饿核心方法final boolean readerShouldBlock() {Node h head;Node s h.next;// 后继第一个节点是独占写节点 → 新来读锁要排队return s ! null !s.isShared();}final int fullTryAcquireShared(int currState) {// 完整重试获取读锁逻辑省略细节return -1;}Overrideprotected boolean tryRelease(int releases) {// 写锁释放逻辑int nextc getState() - releases;if (exclusiveCount(nextc) 0) {setExclusiveOwnerThread(null);setState(nextc);return true;}setState(nextc);return false;}Overrideprotected boolean tryReleaseShared(int unused) {// 读锁释放逻辑循环CAS减少读计数for (;;) {int state getState();int nextc state - SHARED_UNIT;if (compareAndSetState(state, nextc)) {return nextc 0;}}}}写锁获取tryAcquire若当前已存在写锁w ! 0则判断持有者是否为当前线程是则可重入state加acquires低 16 位增加。若无线程持有写锁但存在读锁sharedCount(state) ! 0写锁获取失败读写互斥否则通过 CAS 尝试将state增加acquires成功则设置独占线程。读锁获取tryAcquireShared若其他线程持有写锁则返回-1失败读写互斥但若当前线程持有写锁读锁可降级获取成功锁降级。在非公平模式下先调用readerShouldBlock()判断是否需要阻塞如果 AQS 等待队列的头部后继节点是独占模式写节点为避免写线程饥饿新来的读线程必须排队。若无需阻塞CAS 尝试将state增加SHARED_UNIT高 16 位1成功则返回1。CAS 失败或需要阻塞则进入fullTryAcquireShared自旋重试。防止写饥饿的巧妙设计readerShouldBlock()检查队列头部是否存在等待的写节点若有则让读线程乖乖排队从而给写线程让出机会。写锁释放tryRelease直接减少写锁计数当低 16 位变为 0 时将独占线程清空并返回true表示写锁完全释放可唤醒后继节点。读锁释放tryReleaseShared通过无限循环 CAS 将state减SHARED_UNIT直到成功。若释放后读计数为 0即nextc 0返回true表示无读线程占用可允许后继写线程获取。五、公平同步器FairSync公平模式下所有线程必须严格按队列顺序获取锁。FairSync的实现只需在tryAcquire和tryAcquireShared中增加对前驱节点的检查。代码框架如下具体逻辑与NonfairSync类似仅加入hasQueuedPredecessors()判断static final class FairSync extends Sync {private static final long serialVersionUID -1828979586444678633L;Overrideprotected boolean tryAcquire(int acquires) {// 公平写锁先判断队列是否有前驱节点return false;}Overrideprotected int tryAcquireShared(int acquires) {// 公平读锁实现return -1;}Overrideprotected boolean tryRelease(int releases) {return false;}Overrideprotected boolean tryReleaseShared(int releases) {return false;}}注意本文为聚焦核心思想公平版本的方法体均返回默认值读者可自行参考 JDK 源码补充完整逻辑。六、锁的门面类ReadLock 与 WriteLock这两个内部类实现了Lock接口但不直接维护任何同步状态而是将所有操作委托给外部类的sync对象。这使得读写锁能共用同一个等待队列保证读写互斥的正确性。读锁 ReadLockpublic static class ReadLock implements Lock {private final ReentrantReadWriteLock rwLock;protected ReadLock(ReentrantReadWriteLock rw) {rwLock rw;}Overridepublic void lock() {rwLock.sync.acquireShared(1);}Overridepublic void unlock() {rwLock.sync.releaseShared(1);}}lock()调用 AQS 的acquireShared该方法内部会先调用tryAcquireShared失败则进入同步队列等待。unlock()调用releaseShared最终触发tryReleaseShared并唤醒后续节点。写锁 WriteLockpublic static class WriteLock implements Lock {private final ReentrantReadWriteLock rwLock;protected WriteLock(ReentrantReadWriteLock rw) {rwLock rw;}Overridepublic void lock() {rwLock.sync.acquire(1);}Overridepublic void unlock() {rwLock.sync.release(1);}}lock()调用 AQS 的独占模式acquire其中tryAcquire由子类实现。unlock()调用release递减写计数并可能释放锁。七、锁升降级的支持与限制ReentrantReadWriteLock明确支持锁降级从写锁降级为读锁但禁止锁升级从读锁升级为写锁。理解这两者的差异是掌握读写锁用法的关键。锁升级读 → 写被禁止1. 表面原因读写并发冲突如果允许多个线程同时持有读锁而其中一个线程试图升级获取写锁一旦成功就会出现“一个线程写、其他线程仍在读”的局面直接违反读写互斥的语义。2. 真正致命的原因死锁风险造成锁升级被禁用的根本原因不仅仅是瞬时的读写冲突而是一种典型的资源互相等待导致的死锁线程 A、线程 B 同时持有读锁A 尝试升级写锁必须等待所有读锁释放包括 B 手中的读锁于是 A 阻塞B 此时也尝试升级写锁同样必须等待所有读锁释放包括 A 手中的读锁B 也阻塞。此时 A 与 B 各自握着一部分读锁不放又都在等待对方释放形成永久死锁。JDK 设计者选择直接禁用锁升级从根源杜绝此类场景而不仅仅是防止瞬时读写共存。锁降级写 → 读被允许写锁是独占的同一时刻只有一个线程持有写锁。该线程若在持有写锁期间继续获取读锁因为不存在其他并发持有读锁的线程不会产生读写冲突也不会引发死锁。标准流程为获取写锁 → 获取读锁 → 释放写锁中间短暂阶段该线程同时持有写锁和读锁两种锁共存最终效果是从独占写锁平滑过渡到共享读锁保证数据的连续可见性。注意锁降级必须严格遵循上述顺序不能先释放写锁再获取读锁。若先释放写锁中间可能被其他写线程插入修改数据导致后续读到的数据不再一致。ReentrantReadWriteLock在代码层面通过tryAcquireShared的判断逻辑实现了降级支持if (exclusiveCount(state) ! 0 getExclusiveOwnerThread() ! Thread.currentThread()) {return -1;}若持有写锁的正是当前线程则允许继续获取读锁从而安全地完成锁降级。区分两个概念共存与锁升降级经常有人将“读写锁共存”与“锁升降级”混淆需要明确锁共存指同一时刻系统中同时存在读锁和写锁。由于读写互斥正常情况下不允许共存唯一的例外是锁降级的中间状态即一个线程同时持有写锁和读锁自己和自己共存。锁升降级指线程主动改变锁的模式。升级是从读到写降级是从写到读。前者被禁止后者被允许。理解这两者的区别有助于在面试和实际开发中准确阐述ReentrantReadWriteLock的设计约束。八、总结ReentrantReadWriteLock通过一个精妙的state高低位拆分、两个门面锁的委托模式以及 AQS 的模板方法实现了高效的读写锁语义。其非公平模式还通过readerShouldBlock()巧妙避免写线程饥饿。理解这套源码不仅有助于正确使用读写锁更能加深对 AQS 同步器框架的掌握。核心要点回顾状态拆分高 16 位读计数低 16 位写计数。读写互斥存在写锁时其他线程的读/写均被阻塞但当前线程可降级获取读锁。非公平策略新写线程可能直接插队读线程则需观察队列是否已有等待的写节点。可重入性写锁通过getExclusiveOwnerThread判断重入读锁通过每线程维护重入计数不在本文展开可参考完整源码。锁升降级规则锁升级被禁止避免死锁和读写冲突锁降级被允许遵循“获取写锁 → 获取读锁 → 释放写锁”的顺序保障数据可见性。本文提供的代码为ReentrantReadWriteLock核心逻辑的骨架版本省略了部分细节如fullTryAcquireShared的完整自旋、每个线程的读锁重入计数等但已完整呈现了其设计脉络。希望读者能结合 JDK 源码继续深入彻底掌握这一经典并发工具的实现。