StampedLock写锁释放不主动唤醒,依赖等待线程自旋探测state变更、前驱节点链式unpark或后续acquire线程惰性唤醒,以降低释放路径开销并提升可伸缩性。

核心事实是:StampedLock 的写锁释放(unlockWrite)本身不触发任何唤醒操作。 等待线程的“恢复”依赖于后续的自旋探测 + 轻量级轮询 + 惰性唤醒协作机制,而非传统意义上的 park/unpark 信号传递。
写锁释放后,等待者如何感知并继续?
StampedLock 使用一个 volatile long 类型的 state 字段编码锁状态(含版本号、写锁标志、读计数等)。写锁释放仅做两件事:
- 将 state 中的写锁持有位清零(通过 CAS)
- 递增版本号(低 7 位),表示一次锁状态变更
这个 state 变更本身是可见的,但没有显式 unpark。等待线程靠自己不断读取 state 来发现变化。
读节点(乐观读/悲观读)如何“被唤醒”?
悲观读锁(readLock)获取失败时,会进入自旋 + 阻塞循环:
当用户要求为 Java 代码添加注释、修改注释、审查注释、整理注释格式,或要求按照代码注释规范处理 Java 文件时,使用本技能。本技能用于统一生成和维护 Java 类注释模板、按 Git 对比 release 判断版本号、补充每行代码上方中文块注释、调整注释空行规则,并保持 Java 文件 UTF-8 无 BOM 编码。典型触发场景包括:补充类注释、更新 @version、检查 @author 来源、整理行尾注释、把注释放到代码上一行、检查注释是否符合规范、构建前检查 Java 注释风格。
立即学习“Java免费学习笔记(深入)”;
- 先自旋若干次(通常 1~100 次),反复 CAS 尝试获取读计数
- 自旋失败后调用
LockSupport.park(this)进入阻塞 - 但它的唤醒不是由 unlockWrite 触发的,而是由下一个成功获取写锁或读锁的线程,在 acquire 成功后检查队列头并调用
unpark() - 更关键的是:任意线程在调用
tryOptimisticRead()或validate()时,也会读取 state —— 如果此时写锁已释放,乐观读可能直接成功,无需排队
写节点如何竞争到锁?
写锁获取(writeLock)失败后,同样走自旋 + 阻塞流程:
- 自旋阶段持续检测 state 是否为 0(无写锁、无读锁)
- 阻塞前会把自己插入等待队列尾部(CLH-like),并设置前驱节点的
waiter字段 - 真正唤醒它的,是前驱节点在成功获取写锁并退出临界区后,负责 unpark 它的 waiter 字段所指线程
- 也就是说:唤醒是链式、接力式的,由前一个完成操作的线程完成,不是由 unlockWrite 统一广播
为什么设计成这样?
这是为了极致减少写锁释放路径的开销,避免在高并发写场景下因唤醒引发额外竞争:
- unlockWrite 是极简 CAS,无内存屏障扩散、无队列遍历、无 unpark 调用
- 把“谁该醒”和“何时醒”的决策权交给等待者自身或其前驱,提升可伸缩性
- 代价是:唤醒有延迟(最多一个调度周期),且存在虚假唤醒风险(需配合 state 再检查)

















