乐观读失败后应先重试1–2次,再降级为悲观读;validate()返回false时不可直接用旧值,且tryOptimisticRead()的stamp不能用于tryConvertToWriteLock()。

乐观读失败后必须调用 validate() 判断是否仍可安全读取
StampedLock 的乐观读不是锁,只是记录一个时间戳(stamp),后续需靠 validate(stamp) 检查这期间是否有写操作发生。如果返回 false,说明数据可能已被修改,不能直接使用之前读到的值——但很多人误以为此时必须立刻升级为悲观读锁,其实不是。
真正该做的是:先重试乐观读(再次 tryOptimisticRead() + 读数据 + validate()),最多 1–2 次;若仍失败,再走锁升级路径。频繁跳过重试直接加锁,会过早牺牲无锁路径的性能优势。
常见错误现象:
- 读到旧值后没调用 validate() 就直接返回,导致脏读
- validate() 返回 false 后立即阻塞获取 readLock(),忽略了短时重试机会
锁升级必须从乐观读 stamp 转为 readLock(),不能直接申请 writeLock()
“锁转换”在 StampedLock 中仅支持从乐观读 stamp 升级为读锁,或从读锁升级为写锁(通过 tryConvertToWriteLock(stamp))。但注意:tryOptimisticRead() 返回的 stamp 不能直接传给 tryConvertToWriteLock() ——它只接受由 readLock() 或 writeLock() 返回的 stamp。
所以标准退化路径是:
- tryOptimisticRead() → 失败 → readLock() → 读数据 → unlockRead(stamp)
- 若后续需要写,再用该 readLock stamp 调用 tryConvertToWriteLock(stamp)(成功则获得写锁,失败则需先 unlockRead() 再 writeLock())
容易踩的坑:
- 把 tryOptimisticRead() 的返回值(通常是 0 或一个正整数)误当有效 stamp 传给 tryConvertToWriteLock(),结果永远返回 0(失败)
- 在未持有任何锁的情况下,试图用任意数字调用 tryConvertToWriteLock(),逻辑无效且掩盖真实并发问题
tryConvertToWriteLock() 失败时必须手动释放读锁再抢写锁
StampedLock 不支持“自动降级+升级”,tryConvertToWriteLock(stamp) 成功则原读锁被原子替换为写锁,stamp 变为写锁 stamp;失败则 stamp 无效,**原有读锁依然持有**,必须显式 unlockRead(oldStamp),否则造成锁泄漏。
典型正确流程:
long stamp = sl.readLock();
try {
// ... 读操作
long ws = sl.tryConvertToWriteLock(stamp);
if (ws != 0L) {
stamp = ws; // 升级成功,继续写
// ... 写操作
} else {
sl.unlockRead(stamp); // 必须先释放
stamp = sl.writeLock(); // 再抢写锁
// ... 写操作
}
} finally {
sl.unlock(stamp); // 统一释放(stamp 此时是读或写锁)
}
性能影响:
- tryConvertToWriteLock() 是非阻塞尝试,失败率高时,反复 unlock + writeLock 会增加上下文切换开销
- 如果写操作占比超过 15%,通常说明乐观读+读锁的混合模式并不适合该场景,应直接用 ReentrantReadWriteLock 或更粗粒度设计
StampedLock 不支持重入,嵌套调用会破坏 stamp 有效性
所有 stamp 都是单次、不可重入的凭证。同一个线程重复调用 readLock() 会返回新 stamp,但旧 stamp 仍有效——这意味着你可能用旧 stamp 去 unlockRead(),而实际该锁早已被覆盖,引发 IllegalMonitorStateException 或静默失效。
使用场景限制明显:
- 不适用于递归算法、AOP 代理、或任何隐式重入的框架调用栈
- 日志、监控、异常处理中若意外触发二次读锁,极易污染当前 stamp 状态
建议做法:
- 把 stamp 视为“作用域绑定值”,只在最内层 try-with-resources 或 try/finally 中生成和销毁
- 避免将 stamp 存为字段或跨方法传递,尤其不要在 lambda 或异步回调中使用外部捕获的 stamp
复杂点在于:stamp 的生命周期完全由使用者管理,JVM 不校验、不回收、不出错提示——出问题时往往表现为偶发数据不一致或线程挂起,很难复现。

















