StampedLock 通过乐观读机制避免线程阻塞,适合读多写少、读操作耗时短的场景;它用戳记(stamp)标识状态,支持乐观读、悲观读和写锁三种模式,乐观读需验证stamp有效性,失败后须降级为悲观读。

StampedLock 通过乐观读机制避免了读写锁的线程阻塞,适合读多写少且读操作耗时短的场景。它不直接继承 Lock 接口,而是用戳记(stamp)标识锁状态,支持三种模式:乐观读、悲观读、写锁。关键在于——乐观读失败后要降级为悲观读,不能假定“读到了最新值”就直接返回。
理解乐观读的原理和适用边界
乐观读不是加锁,而是记录一个时间戳(stamp),之后验证该 stamp 是否仍有效(即期间无写操作发生)。如果验证通过,说明读取期间没有写入干扰,数据一致;否则需重试或升级为悲观读。
- 乐观读适用于计算快、冲突少的场景,比如读取几个字段做简单判断
- 不能用于需要强一致性或长耗时读取(如遍历集合、IO 操作),因为 stamp 可能早已失效
- 乐观读不阻塞写线程,写操作会立即使之前所有乐观读 stamp 失效
正确使用乐观读的典型模式
标准流程是:尝试乐观读 → 验证 stamp → 失败则获取悲观读锁重读。注意 validate() 必须在读取完成后立刻调用,且不能跨方法或保存 stamp 到后续逻辑中。
- 先调用 tryOptimisticRead() 获取初始 stamp
- 读取共享变量(尽量轻量,避免副作用)
- 立即调用 validate(stamp) 判断是否仍有效
- 若 validate 返回 false,说明期间有写入,需用 readLock() 获取悲观读锁再读一次
写操作必须用 writeLock 配合 stamp 管理
写锁获取后会生成新 stamp,同时使所有未完成的乐观读 stamp 失效。写操作结束后需显式 unlock,且 stamp 不可复用。
立即学习“Java免费学习笔记(深入)”;
- 写操作始终用 writeLock()/writeLockInterruptibly(),获取 stamp 后执行修改
- 修改完成后必须调用 unlockWrite(stamp),否则锁不会释放
- 不要把写锁 stamp 传给其他线程或缓存,stamp 是一次性凭证
一个安全的乐观读示例
以下是一个计数器读取的典型用法,展示如何避免 ABA 和过期读:
class Counter {
private final StampedLock lock = new StampedLock();
private int value;
int get() {
long stamp = lock.tryOptimisticRead(); // 1. 尝试乐观读
int current = value; // 2. 快速读取
if (lock.validate(stamp)) // 3. 立即验证
return current;
// 验证失败,降级为悲观读
stamp = lock.readLock(); // 4. 获取读锁
try {
return value;
} finally {
lock.unlockRead(stamp);
}
}
void increment() {
long stamp = lock.writeLock(); // 获取写锁
try {
value++;
} finally {
lock.unlockWrite(stamp); // 必须释放
}
}
}



















