AtomicReference 的 CAS 操作本质是 compareAndSet(V, E, N),即仅当当前值等于预期值时才原子更新,必须配合 do-while 循环重试、不可变状态对象封装及显式业务校验才能实现真正无锁安全。

AtomicReference 的 CAS 操作本质是 compareAndSet(V, E, N)
它不是“直接改值”,而是原子性地执行「如果当前值等于预期值,才设为新值」。这意味着你无法绕过状态校验——哪怕只是想给一个字段加 1,也得先读、再算、再 CAS,失败就得重试。很多初学者误以为 AtomicReference 像普通变量一样能链式赋值或批量更新,结果写出非原子的中间态逻辑,比如先改对象某个字段再 set,这完全破坏了无锁前提。
典型错误现象:compareAndSet 频繁返回 false,但没做重试或 fallback;或者把多个字段更新拆成两次 compareAndSet,导致中间态被其他线程读到脏数据。
- 必须用不可变对象(如自定义
State类)封装所有相关字段,让一次compareAndSet覆盖整个业务状态 -
compareAndSet的预期值必须是上一次get()的结果,不能复用缓存的老值 - 若业务逻辑涉及计算(如“余额减扣后不能为负”),需在循环体内做条件判断,失败则重新读取最新状态再试
实现带业务校验的无锁更新必须用 do-while 循环重试
CAS 天然适合乐观场景,但业务规则(比如库存不能超卖、状态只能从 WAITING → PROCESSING)必须由你显式编码进重试逻辑里,JVM 不会自动帮你拦住非法流转。不写循环,compareAndSet 失败就等于放弃更新——这不是并发安全,这是丢数据。
使用场景:订单状态机推进、分布式任务调度器的状态跃迁、共享资源配额扣减。
- 每次循环开头调用
get()获取当前完整状态对象 - 基于当前状态做业务判断(如
if (current.status == WAITING && current.remain >= need)) - 构造新状态对象(务必新建实例,不可复用或修改原对象)
- 调用
compareAndSet(current, next),失败则继续下一轮
示例片段:
do {
State current = ref.get();
if (current.status != State.WAITING || current.balance < amount) {
throw new IllegalStateException("Insufficient balance or invalid state");
}
State next = new State(State.PROCESSING, current.balance - amount);
} while (!ref.compareAndSet(current, next));AtomicReference 无法保证复合操作原子性,多变量更新必须打包成单个对象
有人试图用两个 AtomicReference 分别管「余额」和「版本号」,再靠两次 CAS 串行更新——这是错的。两次 CAS 之间存在时间窗口,其他线程可能在中间修改任一字段,导致状态不一致。CAS 只保证「对同一个引用地址的一次写入」是原子的,不负责跨引用协调。
参数差异:传给 compareAndSet 的 expectedValue 和 newValue 必须是同一类不可变对象实例,且所有业务相关字段都得包含在内。哪怕只多一个时间戳或请求 ID,也要进这个对象。
- 避免使用可变对象(如
HashMap或普通class)作为AtomicReference的泛型类型 - 构造新状态时不要复用旧对象字段(比如
next.items = new ArrayList(current.items)),否则浅拷贝仍可能引发并发问题 - 如果状态对象过大(如含百级 List),频繁 GC 和对象分配会成为瓶颈,此时应评估是否真需要无锁,或改用分段锁+更粗粒度状态
ABA 问题在业务逻辑中往往比理论更隐蔽
标准 ABA 场景(A→B→A)容易被识别,但业务中常见的是「语义 ABA」:比如订单状态从 CREATED → PAID → REFUNDED → CREATED,表面看又回到 CREATED,但这次的 CREATED 已携带不同上下文(如新支付渠道、不同用户 ID)。如果只比对状态枚举值,CAS 会成功,但业务已错乱。
性能影响:加版本号或时间戳虽能破 ABA,但会让每次更新都强制 bump 字段,增加对象大小和 GC 压力;用 AtomicStampedReference 则要额外维护 stamp,代码更重。
- 优先从业务建模规避:把「重入 CREATED」设计为新状态(如
RECREATED),而非复用旧值 - 若必须用 stamp,确保 stamp 变更与业务关键事件强绑定(如每次支付生成唯一 traceId,作为 stamp)
- 不要用
System.nanoTime()当 stamp——精度不够且可能回拨
真正难处理的从来不是 CAS 指令本身,而是你怎么定义“一次完整的业务状态”,以及是否愿意为它付出对象建模和重试逻辑的成本。

















