reentrantreadwritelock 使用同一aqs同步队列管理读写线程,state字段高低16位分别记录读锁数和写锁重入次数,通过cas原子更新;写线程释放后仅唤醒队首节点,读线程释放后传播唤醒后续共享节点,且禁止读锁升级、允许写锁降级。

ReentrantReadWriteLock 的排队机制基于 AQS,但它不是简单复用单一队列,而是通过同一同步队列(CLH)同时管理读线程(共享模式)和写线程(独占模式),关键在于 状态变量的分段设计 和 acquire/release 逻辑的差异化处理。
state 字段的双角色设计
AQS 的 state 是一个 int 值,在 ReentrantReadWriteLock 中被拆分为两部分:
- 低 16 位(0–65535)记录写锁重入次数(独占锁)
- 高 16 位(0–65535)记录持有读锁的线程数(共享锁)
这种拆分使得读写状态可独立变更,且能原子判断:比如 state == 0 表示无锁;(state & 0xFFFF) != 0 表示写锁被持;state >> 16 != 0 表示有读锁在用。所有更新都通过 CAS 完成,避免竞态。
排队时的公平性与唤醒策略
无论读还是写线程,失败后都会进入 AQS 的同步队列尾部,遵循 FIFO 基本顺序。但唤醒逻辑有区别:
- 写线程释放后,只唤醒队首第一个节点(因为写锁是独占的,必须严格串行)
- 读线程释放后,会尝试连续唤醒后续所有处于共享模式的节点(即“传播唤醒”),只要当前无写锁占用且队列中存在读请求
- 若队首是写线程,即使后面全是读线程,也不会跳过它去唤醒——这是保证写不饥饿的关键(尤其在非公平模式下,写线程仍可能插队成功)
读写线程共用队列但行为隔离
节点本身不标记“读/写”,而是由其 acquire 方式决定模式:
- 写线程调用
tryAcquire→ 进入独占流程:检查 state 低 16 位是否为 0,或是否是当前线程重入 - 读线程调用
tryAcquireShared→ 进入共享流程:检查是否有写锁(低 16 位非 0)、是否超过最大读线程数、是否允许重入(如已持写锁则可降级) - 节点入队后,AQS 不区分类型,但
shouldParkAfterFailedAcquire和unparkSuccessor会根据前驱节点的模式决定是否阻塞或唤醒
降级与升级的特殊约束
ReentrantReadWriteLock 允许写锁降级为读锁(先写后读),但禁止读锁升级为写锁(否则必然死锁):
- 降级时,写锁释放后立即获取读锁,期间 state 高 16 位递增,低 16 位清零,整个过程不释放队列节点,避免被其他线程抢占
- 如果某线程已持读锁,再尝试获取写锁,会直接失败并入队——此时它作为写请求参与竞争,不会因已有读锁而获得优先权
- 这种设计让队列语义保持清晰:每个节点只代表一次 acquire 尝试,模式由尝试方式决定,而非线程身份
不复杂但容易忽略的是:共享锁的“批量唤醒”依赖于 doAcquireSharedInterruptibly 中的循环传播逻辑,而独占锁的唤醒永远只触发一次;两者共存于同一队列,靠 state 解耦与 acquire 方法分离来维持一致性。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











