Java 面试题

Java CAS原理和ABA问题

"CAS是什么?ABA问题是什么?怎么解决?"

为什么面试官会问这道题

CAS是Java无锁编程的基石。ABA问题考你是否理解CAS的根本弱点,和知道AtomicStampedReference这类针对性工具。

如何回答

  1. 1

    CAS是CPU原子指令——compare-and-swap:如果地址X当前值等于期望E就原子地写入N并返回成功,否则不动返回失败。

  2. 2

    Java通过Unsafe(或9+的VarHandle)暴露。AtomicInteger、AtomicLong、ConcurrentHashMap都基于它。典型用法是while循环:读当前、算新值、CAS;失败说明别人改过了,重试。

  3. 3

    ABA问题:T1读到A准备CAS;T2把A改成B再改回A;T1的CAS看到A成功,但中间状态变过了,T1的视图已过期。

  4. 4

    具体场景:无锁栈。T1想把head从A改成A.next。中间T2 pop A、pop B、又push A(内存复用),head又是A但链表完全不一样了。T1的CAS成功但把head设成了一个过期的next指针,栈就乱了。

  5. 5

    解法:AtomicStampedReference给值配一个单调递增的stamp,CAS同时比对值和stamp。中间任何变化都会bump stamp,ABA就能被检测到。

参考回答示例

CAS是硬件原子指令——compare-and-swap,语义是"地址X的当前值如果等于期望E,原子地替换成N返回成功;否则不动返回失败"。是非阻塞并发的基石。Java通过Unsafe或VarHandle访问,AtomicInteger、AtomicLong、ConcurrentHashMap的桶CAS都是它。典型用法是重试循环:读当前值、算新值、CAS;有人在中间改了就CAS失败,重试。ABA问题是CAS的根本弱点。线程T1读到值A准备CAS,还没执行时T2把A改成B再改回A。T1终于执行CAS,看到A成功——但底层状态经过了中间值,现在的A可能语义上和T1当初看到的A不一样了。对原子整数计数器这种场景几乎没影响,对引用的无锁数据结构就是灾难。经典例子:AtomicReference做head的无锁栈。T1读head=A,计划把head从A改成A.next。这时T2 pop A(head变B)、pop B(head变null)、新分配一个节点(正好复用了A的地址)push进来(head又是A)。T1的CAS看到A成功,把head设成A.next——但A.next是原始视角里的B,现在已经被pop、被free了。栈的head指向了无效内存。解法是AtomicStampedReference:包一对(值+int stamp)。每次set都bump stamp,compareAndSet同时比值和stamp。任何中间写都会改stamp,即使值回到A也会被检测到,CAS失败重试。AtomicMarkableReference类似,包boolean mark,适合"删除"标记。实战里原子计数器不用管这个,引用类的无锁结构必须用。

实用技巧

  • ABA一定要举具体的栈或队列例子——光说"有个问题"像背书。

  • 能点名AtomicStampedReference是合适的具体度。

  • 区分ABA敏感场景(引用/内存)和不敏感场景(原子计数器)。

  • 忘了AtomicStampedReference,即答侠可以实时提示。

常见问题

CAS和锁性能差多少?

低竞争下CAS快很多(无系统调用无线程park)。高竞争时重试堆积锁反而赢,LongAdder用分片解决这个问题。

CAS会引起cache一致性流量吗?

会——CAS独占cache line,同一个变量高竞争会在多核间cache line ping-pong严重降速。

会有虚假失败吗?

某些架构(ARM LL/SC)下CAS会因为上下文切换或缓存事件虚假失败——必须循环重试,标准while(!CAS)模式已经覆盖了。

面试时担心忘词?即答侠实时助你

即答侠 AI 实时监听面试对话,自动识别问题并即时生成回答建议——无感辅助,让你从容应对每一道题。

免费试用即答侠