当前位置:首页 > 文章列表 > 文章 > java教程 > AtomicStampedReference 怎样检测 ABA

AtomicStampedReference 怎样检测 ABA

来源:17golang原创 2026-10-25 13:28:48 0浏览 收藏

Java 里的 ABA 问题,核心不是“引用最后是不是又变回 A”,而是“这次 A 还是不是之前那次状态”。如果只用 AtomicReference 比较对象引用,A→B→A 可能让线程误以为期间没有发生变化。AtomicStampedReference 把引用和一个 int 类型的 stamp 原子绑定,CAS 时同时检查两项,正适合记录这种状态回转。

官方地址:https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/concurrent/atomic/AtomicStampedReference.html

下面只讨论一个边界:如何读取引用与 stamp 的配对快照,以及如何用 compareAndSet 检测 ABA。它不是事务管理器,也不会自动理解对象内部字段是否发生变化。

先把 ABA 拆成引用身份和 stamp

假设共享位置最初保存对象 A,stamp 是 10。线程甲读取到“引用 A + stamp 10”后暂时停顿;线程乙先把它改成 B 并把 stamp 改成 11,再把引用改回 A,同时把 stamp 改成 12。此时从引用字段单独看,当前值确实又是 A,但完整状态已经从 (A, 10) 变成了 (A, 12)。

Java SE 25 API 对 AtomicStampedReference 的定义就是原子维护对象引用和整数 stamp。这里的 stamp 不是对象内容摘要,而是由更新方维护的状态标记;只要每次相关状态变化都分配不同的标记,读取方就能发现“引用回来了,但版本不一样”。

AtomicStampedReference 引用身份与 stamp 配对状态说明图
图1:AtomicStampedReference 的 ABA 状态配对说明图,展示引用回到 A 时 stamp 仍已变化;这是原创结构图,不是运行截图。

用 get 读取一份配对快照

不要先调用 getReference(),再隔一段逻辑调用 getStamp(),然后把两次读取当作同一时刻的快照。更直接的做法是调用 get(int[] stampHolder):返回值是当前引用,stamp 会写入传入数组的第一个元素。数组至少要有一个位置。

import java.util.concurrent.atomic.AtomicStampedReference;

public class AbaSnapshot {
    public static void main(String[] args) {
        Object first = new Object();
        // 初始状态把引用和版本号绑定在同一个原子容器里。
        AtomicStampedReference state =
                new AtomicStampedReference<>(first, 10);
        int[] stampHolder = new int[1];

        // get 返回引用,并把同一份配对状态的 stamp 写入 holder。
        Object observed = state.get(stampHolder);
        int observedStamp = stampHolder[0];

        System.out.println(observed == first);
        System.out.println(observedStamp);
    }
}
  

这里的 observed 与 observedStamp 应一起保存,作为后续 CAS 的 expected 值。若代码只保存 observed 而丢掉 stamp,后续又会退化成只比较引用。

用 compareAndSet 同时检测两项

compareAndSet 的四个参数分别是期望引用、新引用、期望 stamp 和新 stamp。官方 API 明确要求:当前引用要与期望引用使用 == 相等,同时当前 stamp 也要等于期望 stamp,两个条件都满足才会原子更新。

import java.util.concurrent.atomic.AtomicStampedReference;

public class AbaCasCheck {
    public static void main(String[] args) {
        Object first = new Object();
        Object second = new Object();
        AtomicStampedReference state =
                new AtomicStampedReference<>(first, 10);

        int[] holder = new int[1];
        Object expected = state.get(holder);
        int expectedStamp = holder[0];

        // 模拟别的线程完成 A→B→A,并为每次逻辑变化递增 stamp。
        state.set(second, 11);
        state.set(first, 12);

        // 引用虽回到 first,但旧 stamp=10 已不匹配,所以 CAS 应失败。
        boolean updated = state.compareAndSet(
                expected, second, expectedStamp, expectedStamp + 1);
        System.out.println(updated);
    }
}
  

这个例子用 set 模拟其他线程的变化,重点是 CAS 的判定条件,不是把它当作并发测试结果。线程甲保存的是 (first, 10),当前配对状态是 (first, 12),因此即使引用身份仍然是同一个 first,stamp 不相等也足以拒绝更新。

AtomicStampedReference get 快照与 compareAndSet 双条件关系说明图
图2:AtomicStampedReference 双条件 CAS 说明图,展示引用身份与 stamp 如何共同参与判定;这是原创结构图,不是运行截图。

每次相关状态变化都要更新 stamp

AtomicStampedReference 不会替调用方自动递增 stamp。使用 set、compareAndSet 或其他更新入口时,业务代码必须决定新 stamp。一个常见约定是成功更新后使用旧值加一,并让所有会改变“可被 CAS 观察的状态”的路径遵守同一约定。

// 读取引用与版本后,基于同一份快照尝试提交新配对状态。
int[] stampHolder = new int[1];
Object expectedReference = state.get(stampHolder);
int expectedStamp = stampHolder[0];
Object nextReference = new Object();

// 失败表示引用或 stamp 已被其他线程改变,需要重新读取再决定。
boolean success = state.compareAndSet(
        expectedReference,
        nextReference,
        expectedStamp,
        expectedStamp + 1);

失败后不要直接复用旧的 expectedReference 和 expectedStamp 无限重试。应该重新取得配对快照,并重新计算业务是否仍然允许更新;否则虽然避免了 ABA,却可能把已经过时的业务意图反复提交。

这套检测能解决什么,不能解决什么

场景AtomicStampedReference 的作用调用方仍需负责
引用 A→B→Astamp 变化后,旧的引用与旧 stamp 配对不再匹配每次相关更新都维护新 stamp
只修改对象 A 的内部字段不会自动观察字段内容变化把可观察版本作为 stamp 或换用合适的不可变状态
读取引用与版本get(int[]) 返回配对快照不要拆成两次读取后再假定它们属于同一时刻
长期运行的版本计数stamp 只是 int 标记设计溢出、回绕和重试策略,不能把它当无限大的序号

还要注意引用比较是身份比较,不是调用对象的 equals。如果业务需要比较对象内容,应该先把状态设计成适合原子替换的不可变对象,再把该对象引用和 stamp 作为整体更新。

相关问题

stamp 不变但引用变了,CAS 会成功吗?

不会。compareAndSet 要求当前引用身份和当前 stamp 同时满足期望值;任意一项不匹配都会返回 false。

能不能只调用 getReference() 检测 ABA?

不能可靠检测。它只能告诉你当前引用是谁,不能说明引用在读取期间是否经历过其他状态。检测 ABA 至少要把引用和独立 stamp 作为配对快照保存。

为什么不直接把 stamp 当成对象版本?

stamp 只是一个并发判定标记,类型是 int,不会自动跟踪对象内部字段。只有当更新协议明确规定“哪些变化必须更新 stamp”时,它才有检测意义。

最后回到标题:用 AtomicStampedReference 检测 ABA,先通过 get(int[]) 保存引用与 stamp 的配对快照,再用 compareAndSet 同时校验引用身份和 stamp。引用即使回到 A,只要 stamp 已从旧值变化,旧快照就不会被误认为仍然有效。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Normalizer 怎样规范化为 NFCNormalizer 怎样规范化为 NFC
上一篇
Normalizer 怎样规范化为 NFC
BreakIterator 怎样按可见边界截断
下一篇
BreakIterator 怎样按可见边界截断
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    685次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    700次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    753次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    717次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    552次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码