1. 首页
  2. 面试专题
  3. 文章列表
通用技术面 Java 后端 Java CAS 与原子类 2026-07-01

CAS 能避开锁,但避不开竞争:原子类追问要讲到失败路径

CAS 不是一句 compare-and-swap。它解决的是单点原子更新,但失败重试、ABA 和高竞争场景会把问题重新带回工程设计。

很多并发题的回答会停在一句话:CAS 就是比较并交换,不加锁也能保证原子性。这个定义没错,但它只回答了入口问题。真正的追问通常会往后走:CAS 失败会怎样?一直失败会不会浪费处理器资源?ABA 是什么?AtomicInteger 能不能保护一段业务逻辑?为什么有些计数场景会用 LongAdder?

如果这些问题答不出来,面试官会判断你只是记住了概念,没有理解无锁编程的成本。CAS 的重点不是“没有锁”,而是它把阻塞等待变成了条件更新和失败重试。这个转换带来性能机会,也带来新的边界。

Java CAS 重试边界图
Java CAS 重试边界图

CAS 保护的是一次内存位置的更新

以 AtomicInteger 的自增为例,线程会先读到一个旧值,计算新值,再尝试把内存里的值从旧值改成新值。如果期间没有其他线程改过,更新成功;如果已经被改过,就重新读取、重新计算、重新尝试。

这能保证单个变量的一次更新是原子的,但不要把它扩大成“所有并发问题都能用原子类解决”。比如扣库存不仅是一个数字减一,还涉及库存是否足够、订单状态、流水记录、失败回滚和幂等。多个状态之间要保持一致时,单个 AtomicInteger 往往不够,需要锁、事务、状态机或队列来保证更大的不变式。

失败重试不是免费午餐

CAS 在低竞争场景下很轻,因为线程不用进入阻塞和唤醒流程。但竞争激烈时,大量线程同时更新一个热点变量,会频繁失败并重试。线程看起来都很忙,实际很多时间花在反复读、比较、失败、再读上。

这就是为什么面试里不能只说 CAS 性能好。更准确的说法是:CAS 适合冲突不高、临界区很小、更新逻辑简单的场景。如果所有请求都抢同一个变量,或者更新逻辑很复杂,CAS 的优势会下降,甚至会因为空转造成处理器压力。

ABA 要讲清楚适用范围

ABA 的问题是:一个值从 A 变成 B,又变回 A。CAS 只看到当前值仍然是 A,就以为没有变化过。对于普通递增计数,这通常不是关键问题;但对于引用、栈节点、状态流转这类场景,中间变化可能很重要。

解决思路不是机械背一个类名,而是增加版本或标记,让“值相同但过程不同”能被识别出来。比如带版本号的引用更新,就不是只比较对象值,还要比较版本。面试里把场景讲清楚,比直接说 AtomicStampedReference 更重要。

LongAdder 不是 AtomicInteger 的全面替代

高并发计数里,LongAdder 通过分散热点减少多个线程争抢同一个值,适合统计请求数、指标计数这类写多读少的场景。但它也有边界:在并发更新过程中,读取到的总和不一定是一个强一致瞬间快照。

如果这个数字只是监控指标,短暂误差通常可以接受;如果它决定是否允许扣库存、是否超过额度、是否触发一次性业务动作,就不能只为了吞吐把一致性边界牺牲掉。这类取舍是面试里很容易拉开差距的地方。

锁和 CAS 不是谁淘汰谁

锁的代价是阻塞、唤醒和上下文切换,但锁能表达更大的临界区,也能让多个变量一起受保护。CAS 的代价是失败重试和设计复杂度,它更适合小而明确的原子更新。

一个成熟回答可以这样组织:我会先看共享状态的范围。如果只是单个计数器或状态位,冲突不高,可以考虑原子类;如果涉及多个字段一致性、复杂校验或必须严格串行的业务动作,我会选择锁、事务或消息串行化。并发设计不是追求“无锁”这个标签,而是让正确性边界、性能成本和故障表现都可解释。