StampedLock乐观读通过“猜着读→再验证”两步替代传统加锁,仅用位运算获取stamp、普通内存访问读数据、按位比较validate,全程无CAS、无队列、无内存屏障,从而消除读线程竞争开销,实现接近无锁的高性能读取。

StampedLock 的乐观读不是“去掉锁”,而是把“加锁→读→解锁”这个同步闭环,拆成“猜着读→再验证”两步。它不靠互斥阻塞来保一致,而是靠轻量校验戳(stamp)判断数据是否被改过——验证成功就用本地快照,失败再补锁重读。这一步省掉了所有读线程的 CAS 竞争、队列排队和状态维护开销,真正把读操作降到了接近无锁级别。
乐观读如何绕过传统读锁的同步开销
ReentrantReadWriteLock 的读锁每次获取都要执行 CAS 更新 state、检查写锁状态、可能入队等待——哪怕当前根本没人写,这些动作也照做不误。高并发下大量线程争抢同一个读锁计数器,形成“伪共享”和 CAS 自旋浪费。
- 乐观读调用 tryOptimisticRead() 只做一次位运算:检查 state 最高位(WBIT)是否为 0,是则直接返回当前 stamp 值(本质是低 48 位版本号),全程无 CAS、无内存屏障、不修改任何共享状态
- 读取共享变量时,完全走普通内存访问,不触发 volatile 语义或锁获取的内存栅栏
- 整个过程不参与 CLH 队列调度,不唤醒/挂起线程,也不更新 AQS 类似的等待节点
validate() 校验为何比持锁更轻量
validate(stamp) 不是重新抢锁,只是拿当初拿到的 stamp 和当前 state 做一次按位比较:若 (state & WBIT) == 0 且 (state & SBITS) == stamp,说明期间没发生写操作。这个判断只读一个 long 字段,没有写操作,也没有锁竞争。
- 对比 ReentrantReadWriteLock 中的读锁释放:需 CAS 减计数、检查是否有写线程在等、可能唤醒等待者——三步原子操作
- validate() 是纯函数式判断,可内联、可预测分支,JIT 优化友好;而读锁的 acquire/release 涉及复杂状态机和线程调度路径
- 即使校验失败,也只是触发一次 readLock() 降级,而非每次读都走这套重路径
写线程不再被读操作“饿死”的机制根源
ReentrantReadWriteLock 中,只要有一个读线程持有读锁,写线程就必须等全部读锁释放才能进临界区。而 StampedLock 的乐观读根本不占锁资源,写线程调用 writeLock() 时看到的是“当前无写锁 + 无悲观读锁持有”,就能立即抢占成功。
- 悲观读锁(readLock)仍会阻塞写,但它是可选的、按需使用的;绝大多数读走乐观路径,不构成写线程的障碍
- 写锁获取逻辑只检测 state 是否含 WBIT 和是否已有悲观读锁(通过 RFULL 判断),完全无视乐观读的存在
- 这就实现了“读不阻塞写”的解耦,让写操作延迟从“等所有读者退出”缩短为“等当前唯一写者完成”,吞吐更稳
同步成本降低体现在哪几个关键指标上
实测中,10 读 1 写场景下,乐观读将读 QPS 从 12 万推到 45 万,核心压缩点在三个层面:
- CPU 时间:省去每个读操作平均 2–3 次 CAS 和 1 次 volatile load,减少 cache line 争用
- 上下文切换:无阻塞意味着零线程挂起/唤醒,避免 OS 调度开销
- 内存带宽:避免多个读线程反复刷写同一 cache line(如读锁计数器),降低总线流量

















