StampedLock 的乐观读通过 stamp 验证替代加锁,仅在冲突时退化为悲观读;适用于读多写少、读操作极轻量场景,吞吐量可达 ReentrantReadWriteLock 的 3–4 倍。

StampedLock 的乐观读不是“更快的读锁”,而是根本不用锁——它靠 stamp 验证代替加锁,只在冲突时才退化为悲观读。这在读多写少、读操作极轻量(如读几个字段)的场景下,吞吐量能比 ReentrantReadWriteLock 高 3–4 倍。
为什么 tryOptimisticRead() 返回 0 就该立刻放弃?
tryOptimisticRead() 返回 0 表示当前有写锁正在持有,或刚被释放但 stamp 还未刷新。这不是临时抖动,而是明确的“此刻不满足乐观读前提”。继续往下读字段再验证,validate(0) 必然返回 false,白白浪费一次读取和判断。
- 遇到 0,应直接跳转到
readLock()路径,不要尝试“再试一次”或 sleep 后重试 - 返回 0 的频率高,说明写操作太频繁,或读操作耗时过长(比如读完还要做复杂计算),已不适合用乐观读
- 注意:
tryOptimisticRead()是纯 volatile 读,无任何同步开销;返回 0 本身不构成性能问题,问题出在误用后续逻辑
validate() 必须紧跟在读操作之后,且不能跨方法调用
验证必须紧挨着数据读取,中间不能插入任何可能被重排序的语句(如日志、IO、非 final 字段赋值)。JVM 或 CPU 可能将读字段提前或延后,导致 validate() 检查的不是你实际读的那一份快照。
- 正确顺序:
long stamp = lock.tryOptimisticRead(); int x = this.x; if (lock.validate(stamp)) { ... } - 错误写法:把
x = this.x提前到tryOptimisticRead()之前,或放到另一个 private 方法里 - 字段必须是
volatile或final;普通非 volatile 字段即使 validate 成功,也可能因缺少 happens-before 关系读到陈旧值 - 避免在验证前调用任何可能触发 GC、线程切换或同步块的方法
写操作会立即让所有未 validate 的乐观读失效
每次 writeLock() 成功,内部 state 的 stamp 都会递增(奇数表示写占用态)。这意味着:哪怕写操作只持续几纳秒,所有在它开始前拿到的 stamp,在它结束后的 validate() 都会失败。
- 乐观读只适合读取动作本身很快的场景(微秒级),比如读计数器、坐标、开关标志
- 如果读逻辑包含遍历集合、序列化、网络调用等,写线程极易“插队”,导致频繁降级,反而比直接用
readLock()更慢 - 写锁不阻塞乐观读的获取,但会阻塞其验证——这是它不饿死写线程的关键,也是你不能假设“读完再验证总来得及”的原因
真正难的不是写出乐观读模板,而是判断哪些字段/场景值得用它:stamp 是廉价的,validate 是廉价的,但一次失败的乐观读 + 降级重读,开销接近两次悲观读。所以它只在“95% 以上验证成功”且“单次读极轻量”时才有意义。

















