乐观读不是读写锁轻量版,用错会导致卡死、漏数据、CPU 100%;必须验证失败后降级为readLock(),validate()需紧随读取之后且一次性读全,writeLock()不可重入,stamp不可复用,不满足强一致性场景勿用。

不能直接“替代”,乐观读不是读写锁的轻量版,而是另一套语义——用错就会卡死、漏数据、CPU 100%。
乐观读失效后不降级会无限重试
常见错误是只调用 tryOptimisticRead() + validate(),但验证失败后没走 readLock() 降级,而是直接重试乐观路径。这会导致线程在无锁状态下疯狂轮询,validate() 一直返回 false,CPU 拉满到 100%。
- 必须把降级逻辑包进
if (!sl.validate(stamp)) { ... }分支里,且里面要调用readLock()和配对的unlockRead(stamp) - 别在乐观读验证失败后还尝试“再试一次乐观读”——这不是重试,是自旋陷阱
- 如果写操作稍频繁(比如秒级写入),乐观读命中率低,降级成本反而比直接用
readLock()更高
validate() 必须紧跟读取操作之后、使用之前
这是最容易被忽略的内存语义陷阱。validate() 只保证「从 tryOptimisticRead() 到 validate() 这段区间内没发生写操作」,中间插入任何可能修改状态的操作(哪怕只是调用一个纯函数),都会让校验失去意义。
- 错误写法:
double a = x; someUtilityMethod(); if (!sl.validate(stamp)) { ... }——someUtilityMethod()可能触发 GC、日志、甚至间接触发写操作 - 正确顺序:读共享变量 → 立即
validate()→ 再做计算或返回 - 局部变量必须一次性读全(如
x和y要连续读),否则出现“半新半旧”值(x 是旧的、y 是新的)
写锁不可重入,嵌套调用直接抛 IllegalMonitorStateException
和 ReentrantReadWriteLock 不同,StampedLock 的 writeLock() 是严格不可重入的。同一个线程重复获取写锁,不会阻塞,而是立刻抛 IllegalMonitorStateException,且不会自动清理已持有的锁。
- 别在写锁保护的方法里再调用自身或其他含
writeLock()的方法 - 升级场景必须用
tryConvertToWriteLock(stamp),而不是先unlockRead()再writeLock()—— 中间存在竞态窗口,其他线程可能插进来写 - 如果
tryConvertToWriteLock()返回 0,说明升级失败,此时必须先unlockRead(stamp),再用阻塞式writeLock()
stamp 值绝对不能跨作用域复用或缓存
stamp 是瞬态版本戳,绑定在具体锁操作生命周期上。它不是 token,不是 ID,也不是可序列化的状态。
- 不能把
stamp存成字段、传给其他线程、或在 lambda 里闭包捕获后异步使用 - 每次
tryOptimisticRead()、readLock()、writeLock()都必须拿新 stamp,用完即弃 - 忘记调用
unlockRead(stamp)或unlockWrite(stamp)会导致锁资源泄漏,后续线程可能永久阻塞
真正压榨 CPU 的不是乐观读本身,而是它绕过内存屏障和队列调度的物理特性;代价是放弃强一致性保证。如果你的业务要求“每次读都看到最新写”,那就别碰乐观读——它天生不适合这种场景。










