StampedLock 是读多写少场景的性能补丁,非 ReentrantReadWriteLock 替代品;不支持重入与条件变量;乐观读需 validate 校验;锁升级须用 tryConvertToWriteLock;stamp 严格绑定作用域且不可复用。

StampedLock 的核心定位:不是替代 ReentrantReadWriteLock,而是读多写少场景下的性能补丁
它本质是 Doug Lea 为 JDK 内部工具类(比如 ConcurrentHashMap 的某些分支)设计的“高性能读写协调器”,不是给你直接当通用读写锁用的。如果你只是想加个读写锁防并发修改,ReentrantReadWriteLock 更稳、更直觉、更难出错。
- 适用场景非常明确:读操作极频繁(毫秒级反复调用)、写操作极少(分钟级才一次)、且对读延迟极度敏感(比如高频监控指标聚合、缓存快照)
- 不支持重入:同一个线程重复调用
readLock()或嵌套writeLock()会死锁或抛IllegalMonitorStateException - 没有条件变量支持:不能像
ReentrantLock.newCondition()那样做 await/signal,别想用它做复杂线程协作 - JDK 1.8+ 才有,低版本兼容性需自行兜底
tryOptimisticRead + validate 是唯一正确的乐观读姿势
很多人以为 tryOptimisticRead() 返回非零就代表“读成功了”,其实它只表示“此刻没人在写”,但之后任意时刻都可能被写线程篡改——所以必须在读完数据后立刻校验。
- 错误写法:
long stamp = lock.tryOptimisticRead(); int x = sharedValue; // 没校验直接用→ 读到脏数据风险极高 - 正确流程:先
tryOptimisticRead(),读数据,再validate(stamp);失败则降级为readLock()重读 -
validate()必须在读操作完成后立即调用,中间不能穿插任何可能触发写操作的逻辑(比如调用外部不可信方法) - 乐观读不加锁、不阻塞、不改变锁状态,所以也无需
unlock——但一旦validate()失败,就必须用unlockRead()清理降级后获取的读锁
long stamp = lock.tryOptimisticRead();
int value = data;
if (!lock.validate(stamp)) {
stamp = lock.readLock();
try {
value = data; // 重新读
} finally {
lock.unlockRead(stamp);
}
}
写锁升级必须用 tryConvertToWriteLock,别硬切
从读锁(无论乐观还是悲观)直接转写锁是危险动作,StampedLock 不允许你先 unlockRead 再 writeLock —— 中间存在竞态窗口,其他线程可能插进来写。
- 正确方式是用
tryConvertToWriteLock(long):它原子地尝试将当前持有的读锁升级为写锁,失败返回 0 - 如果升级失败(返回 0),说明已有其他写操作在排队或进行中,此时应释放读锁,再用
writeLock()阻塞等待 - 注意:
tryConvertToWriteLock()只接受由本线程之前获取的合法stamp,传错值或过期 stamp 会静默失败(返回 0),不会抛异常 - 乐观读 stamp 无法升级:只有通过
readLock()获取的真实读锁 stamp 才能参与转换
释放锁时 stamp 匹配错误是静默失效,不是报错
这是最隐蔽的坑:unlockRead(stamp) 或 unlockWrite(stamp) 如果传入了错误、过期或属于别的线程的 stamp,不会抛异常,而是直接忽略——结果就是锁没释放,后续线程卡死。
立即学习“Java免费学习笔记(深入)”;
- 每个
stamp是单次有效的“能力凭证”,不能复用、不能跨线程传递、不能缓存后多次使用 - 写锁必须用
writeLock()返回的 stamp 调用unlockWrite(),不能混用 readLock 的 stamp - 读锁同理:
readLock()得到的 stamp 只能用于unlockRead();tryOptimisticRead()的 stamp 不能传给任何 unlock 方法 - 建议把 stamp 和临界区代码严格绑定在同一个作用域内,避免变量污染或误传
它的机制很薄——没有锁计数、没有持有者追踪、没有自动清理。用错 stamp 就像拿错钥匙开门,门没开,但你不知道是钥匙错了,还是门坏了。


















