Java StampedLock 乐观读校验失败的回退方式
StampedLock 乐观读校验失败后,不要继续使用第一次读到的局部变量,也不要只循环调用 validate。正确回退方式是获取普通读锁,在读锁保护下把相关共享字段全部重新读取一遍,再在 finally 中用新的读锁 stamp 释放锁。
官方 API 文档:https://docs.oracle.com/en/java/javase/24/docs/api/java.base/java/util/concurrent/locks/StampedLock.html
- 调用
tryOptimisticRead()取得观察 stamp。 - 把需要的共享字段复制到局部变量。
- 调用
validate(stamp)校验观察窗口。 - 失败时获取
readLock(),并在锁内重新读取全部字段。 - 只对真正的读锁 stamp 调用
unlockRead()。
可以直接套用的回退模板
下面的 Counter 同时维护订单数和总金额。读取端要么得到一次有效乐观观察中的两个值,要么回退到读锁后重新取得一致快照。
import java.util.concurrent.locks.StampedLock;
public final class Counter {
private final StampedLock lock = new StampedLock();
private long orderCount;
private long totalAmount;
public Snapshot snapshot() {
// 先取得乐观观察 stamp;写锁正被持有时可能直接返回 0。
long stamp = lock.tryOptimisticRead();
long count = orderCount;
long amount = totalAmount;
if (!lock.validate(stamp)) {
// 校验失败说明观察期间出现过写锁,回退到普通读锁。
stamp = lock.readLock();
try {
// 必须在读锁内重新读取,不能沿用前面可能不一致的局部值。
count = orderCount;
amount = totalAmount;
} finally {
// 此处 stamp 表示真正持有的读锁,因此需要释放。
lock.unlockRead(stamp);
}
}
return new Snapshot(count, amount);
}
public void add(long amount) {
long stamp = lock.writeLock();
try {
// 两个字段作为一个状态整体更新。
orderCount++;
totalAmount += amount;
} finally {
lock.unlockWrite(stamp);
}
}
public static final class Snapshot {
public final long orderCount;
public final long totalAmount;
private Snapshot(long orderCount, long totalAmount) {
// 快照使用不可变字段,返回后不再依赖共享状态。
this.orderCount = orderCount;
this.totalAmount = totalAmount;
}
}
}
当 tryOptimisticRead() 返回 0 时,validate(0) 一定失败,这段代码会自然进入读锁分支,不需要单独再写一套判断。

为什么顺序必须是先读字段、再 validate
validate(stamp) 的含义是:从这个 stamp 产生以后,锁是否被以写模式获取过。校验成功会为此前的乐观读取建立相应的内存可见性保证;它不是一把持续持有的读锁。
如果先 validate,再去读字段,写线程完全可以在校验结束后立刻获取写锁并更新数据。此时后续读取又落在无保护区间,校验结果已经不能覆盖它。
public Snapshot wrongSnapshot() {
long stamp = lock.tryOptimisticRead();
if (lock.validate(stamp)) {
// 错误:校验结束后才读共享字段,写线程可能已在两者之间完成更新。
return new Snapshot(orderCount, totalAmount);
}
// 这里只是示例错误分支,不能作为实际回退方案。
throw new IllegalStateException("optimistic read failed");
}
乐观读不是“先检查现在有没有写线程”,而是对一个已经完成的短观察区间做事后确认。这个区间应尽量短,只做字段读取和轻量局部复制。
为什么获取读锁后还要重新读取
校验失败意味着乐观观察期间有写锁获取过。此前复制到 count 和 amount 的值可能来自不同状态,不能因为现在已经拿到读锁就自动变正确。读锁只能保护拿锁之后的读取,所以要在锁内覆盖全部局部变量。
| 写法 | 问题 | 处理 |
|---|---|---|
| validate 失败后直接返回旧局部变量 | 可能返回混合状态 | 获取读锁并重新读取全部字段 |
| 只重新读取其中一个字段 | 快照仍可能跨越两次状态 | 把同一不变量中的字段作为整体重读 |
| 对乐观 stamp 调用 unlockRead | 乐观 stamp 不代表持有读锁 | 仅在 readLock 成功后解锁 |
| 在失败后反复自旋 validate | 旧 stamp 不会因等待而恢复有效 | 取得新 stamp 重试,或直接回退读锁 |

不能无限等待时,用限时读锁回退
readLock() 可能阻塞。如果读取发生在延迟敏感的接口中,可以使用带超时的 tryReadLock。返回 0 表示在限定时间内没有拿到读锁;线程被中断时要恢复中断标记或继续向上抛出。
import java.util.concurrent.TimeUnit;
public Snapshot snapshotWithin(long timeoutMillis) throws InterruptedException {
long stamp = lock.tryOptimisticRead();
long count = orderCount;
long amount = totalAmount;
if (lock.validate(stamp)) {
// 乐观观察有效,直接返回局部快照。
return new Snapshot(count, amount);
}
// 回退分支最多等待指定毫秒数,避免读线程无限阻塞。
long readStamp = lock.tryReadLock(timeoutMillis, TimeUnit.MILLISECONDS);
if (readStamp == 0L) {
throw new IllegalStateException("无法在限定时间内取得读锁");
}
try {
// 成功持有读锁后重新读取所有相关字段。
return new Snapshot(orderCount, totalAmount);
} finally {
lock.unlockRead(readStamp);
}
}
不要把超时后的异常文案写成“锁已死锁”。StampedLock 不提供公平性保证,短暂写竞争、线程调度和真正的嵌套锁问题都可能导致超时,需要结合调用路径进一步判断。
这些场景不适合直接套乐观读
- 读取会触发副作用:乐观区间可能被放弃,不应在其中修改计数器、发送消息或调用不可重复方法。
- 遍历复杂可变对象图:官方文档提醒乐观读取到的字段可能高度不一致;引用、数组元素和对象方法需要更严格的结构知识。
- 方法可能再次获取同一把锁:
StampedLock不可重入,锁内调用未知方法可能让线程永久等待。 - 写入频率很高:乐观校验经常失败时,反复读取再回退只增加开销,直接读锁可能更简单。
- 需要 Condition:
StampedLock的 Lock 视图不支持newCondition()。
一段可复用的检查清单
- 共享字段是否先复制到局部变量,再调用 validate。
- validate 失败后是否获取读锁并重读全部相关字段。
- 读锁是否始终在 finally 中使用正确 stamp 释放。
- 乐观区间是否只包含短小、只读、可丢弃的操作。
- 是否避免在持锁区调用可能再次获取同一 StampedLock 的方法。
- 高写入场景下是否实际需要乐观读,而不是直接使用读锁。
相关问题
validate 返回 false 之后,能不能继续 validate 同一个 stamp
不能指望旧 stamp 恢复有效。它已经说明观察期间出现过写锁;应重新取得新的乐观 stamp,或按本文模板回退到读锁。
乐观读校验成功就一定看到最新值吗
它保证这次观察窗口没有被写锁获取破坏,并不等价于业务层面的“全局最新”。如果业务要求特定时刻的一致版本,还需要更明确的版本协议。
StampedLock 和 ReentrantReadWriteLock 怎么选
读多写少、读取很短且能安全构造局部快照时,StampedLock 的乐观读才有优势。需要可重入、Condition、清晰所有权或更简单维护时,普通读写锁通常更稳妥。
testing.T.Context 绑定测试清理任务的生命周期
- 上一篇
- testing.T.Context 绑定测试清理任务的生命周期
- 下一篇
- 模糊测试输入触发 panic 后的复现路径
-
- 文章 · java教程 | 4小时前 | 异常处理 · 并发编程 · Java教程 · StructuredTaskScope Java Structured Concurrency Joiner allUntil 子任务异常 聚合异常
- Java Structured Concurrency 汇总子任务异常
- 260浏览 收藏
-
- 文章 · java教程 | 6小时前 |
- Java ForkJoinPool asyncMode 调整任务队列顺序
- 121浏览 收藏
-
- 文章 · java教程 | 16小时前 | 可观测性 · Java教程 · LongAdder JFR事件流 RecordingStream Java延迟指标 近似分位数
- JFR 事件流如何在线聚合延迟指标
- 418浏览 收藏
-
- 文章 · java教程 | 20小时前 | Java · 并发编程 · CompletableFuture · java completablefuture 取消原因 异步编排
- Java CompletableFuture 怎样在阶段之间保留取消原因
- 415浏览 收藏
-
- 文章 · java教程 | 1天前 | spring · Spring Boot · 事务管理 · Java教程 · REQUIRES_NEW Spring事务事件 TransactionalEventListener AFTER_COMMIT Spring事件
- Spring 事务事件监听器如何在提交后执行
- 126浏览 收藏
-
- 文章 · java教程 | 1天前 | 配置管理 · Java · Spring Boot · 故障排查 · Java教程 · spring boot actuator @ConfigurationProperties 配置属性绑定 PropertySource
- Spring Boot 配置属性绑定失败怎样定位字段来源
- 184浏览 收藏
-
- 文章 · java教程 | 1天前 | Java ·
- Java Vector API 怎样批量计算浮点数组
- 319浏览 收藏
-
- 文章 · java教程 | 1天前 | Java · java Class-File API LineNumberTable
- Java Class-File API 如何重写方法的行号表属性
- 418浏览 收藏
-
- 文章 · java教程 | 1天前 | Java · 性能优化 · Java教程 · arena 内存映射 超大文件 FileChannel.map Java MemorySegment
- Java 内存段怎样安全映射超大文件
- 435浏览 收藏
-
- 文章 · java教程 | 1天前 | Java · record pattern Java记录模式 嵌套record Java模式匹配 Java switch模式
- Java 记录模式怎样拆解嵌套数据对象
- 308浏览 收藏
-
- 文章 · java教程 | 1天前 |
- Java 虚拟线程批量发起网络请求时如何限制并发度
- 265浏览 收藏
-
- 文章 · java教程 | 1天前 | Java · 并发编程 · 虚拟线程 · java 结构化并发 StructuredTaskScope 子任务取消
- Java 结构化并发怎样统一取消一组子任务
- 370浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 404次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 481次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 492次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 436次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 262次使用
-
- Java try-with-resources 多个资源关闭顺序是什么
- 2026-09-10 501浏览
-
- 矩阵主副对角线快速定位技巧
- 2026-05-31 501浏览
-
- Java多态优化流程代码与行为分发改进
- 2026-05-26 501浏览
-
- JVM 类元数据双亲委派链表深度解析
- 2026-05-21 501浏览
-
- 反射异常处理:InvocationTargetException解析与应用
- 2026-05-16 501浏览

