stampedlock 是 java 8 引入的带版本号锁,专为读多写少且读操作极轻的场景设计,通过乐观读+validate()机制避免写饥饿,但不支持可重入、条件变量和中断。

StampedLock 是什么?它不是 ReadWriteLock 的替代品
StampedLock 是 Java 8 引入的「带版本号的锁」,核心不是“更高级”,而是为特定场景(读极多、写极少、读操作极轻)提供更低开销的乐观读路径。它不兼容可重入、不支持条件变量、不能被中断——这些不是缺陷,而是设计取舍。
它解决的是 ReentrantReadWriteLock 在高并发读下的写饥饿问题:当一堆线程持续持着读锁,写线程永远抢不到锁。StampedLock 允许写线程直接插队,乐观读线程靠 validate() 自行发现冲突并降级,把阻塞点从“获取锁时”转移到“验证结果时”。
怎么用 tryOptimisticRead()?别把它当普通读锁用
tryOptimisticRead() 返回一个 stamp,但这个 stamp 不是锁,它不阻塞任何线程,也不登记持有者。它只是个“快照时间戳”——你读完数据后,必须立刻调用 validate(stamp) 判断期间有没有写发生。
- 如果
validate()返回true,说明读取过程没被写干扰,数据可信 - 如果返回
false,说明可能有写入,此时必须放弃当前读值,转为悲观读(readLock())重试 - 读操作必须是轻量的,不能含 IO、锁等待或耗时计算,否则
validate()失败概率飙升 - 不能在乐观读期间修改共享状态(比如边读边往集合里 add),因为 validate 只检查写锁状态,不校验数据一致性
示例片段:
立即学习“Java免费学习笔记(深入)”;
long stamp = lock.tryOptimisticRead();
int currentSize = list.size(); // 快速读
if (!lock.validate(stamp)) {
stamp = lock.readLock();
try {
currentSize = list.size(); // 重新读,这次受锁保护
} finally {
lock.unlockRead(stamp);
}
}
writeLock() 和 readLock() 的 stamp 怎么释放?漏传或传错会出事
所有带锁的操作(writeLock()、readLock())都返回非零 stamp,而释放必须传回**同一个 stamp**。这不是可选参数,是强制契约。
- 传错 stamp(比如把 writeStamp 传给
unlockRead())会抛IllegalMonitorStateException - 传 0 或 -1 会静默失败,锁不释放,导致后续线程永久阻塞
- 不能复用 stamp:同一个 stamp 不能 unlock 两次,也不能跨线程传递使用
- 推荐用 try-finally 包裹,且 stamp 变量作用域尽量窄,避免误用
错误写法:
long stamp = lock.writeLock(); // ... 忘记赋值或中途覆盖了 stamp lock.unlockWrite(0); // ❌ 静默失败
为什么 validate() 失败后要重读?而不是直接 throw Exception
因为乐观读的设计哲学是「快速尝试 + 冲突后补偿」,不是「保证一次成功」。validate 失败只表示“可能不一致”,不代表数据一定脏——但 StampLock 不做精细判断,直接让应用层决定是否值得重试。
- 适合场景:读的是基本类型、不可变对象、或能容忍短暂 stale 值的缓存计数器
- 不适合场景:银行余额、库存数量等强一致性要求的数据,应直接用
readLock() - 不要在 validate 失败后 sleep 再重试——这会放大竞争,应立即降级为悲观读
- 注意:乐观读期间发生的写操作,其修改对当前线程不可见;但悲观读后看到的,是写操作完成后的最新值
容易忽略的一点:StampedLock 没有公平策略,所有模式都不保证 FIFO,写线程可能反复插队,导致长尾读线程延迟升高——这不是 bug,是吞吐优先的代价。











