【大白话说Java面试题 第109题】【并发篇】第9题:什么是 CAS?CAS 是如何保证原子性的? 微服务架构基于Spring Cloud Alibaba的分布式事务处理:Seata AT模式与Sentinel协同实现高并发下数据最终一致性第9题什么是 CASCAS 是如何保证原子性的回答核心考点CASCompare And Swap是 Java 并发编程中实现无锁并发的核心机制也是Atomic原子类和AQS框架的基石。大厂面试不会只问CAS 是什么而是深入考察CPU 指令级实现cmpxchgvslock cmpxchg、ABA 问题及其解决方案AtomicStampedReference、自旋开销与自适应自旋、以及 CAS 在高并发下的性能瓶颈缓存行竞争、伪共享。面试官真正想判断的是你是否理解 CAS 从硬件到 JVM 的完整链路以及能否在生产环境中正确选型和使用。1. CAS 的定义与核心原理1.1 什么是 CASCASCompare And Swap比较并交换是一种乐观锁实现机制核心思想是先读取内存值在修改时比较当前值是否与预期值一致一致则更新不一致则重试。CAS 操作涉及三个操作数操作数含义示例VVar主内存中共享变量的当前值AtomicInteger内部的valueOOld线程读取时记录的预期值读取到的value快照NNew期望更新后的新值value 1伪代码逻辑do{oldValueV;// 读取主内存值newValueoldValue1;// 计算新值}while(!compareAndSwap(V,oldValue,newValue));// CAS 原子操作1.2 硬件层面的原子性保障CAS 的硬件基础是 CPU 提供的原子指令不同架构实现不同架构指令说明x86/x64cmpxchg比较并交换指令多核下需加lock前缀保证原子性ARMldrex/strexLoad-Exclusive / Store-Exclusive通过独占监控实现RISC-VLR/SCLoad-Reserved / Store-Conditionalx86 的cmpxchg指令详解; 单核环境cmpxchg 本身原子 cmpxchg eax, ebx ; 比较 eax 和内存值相等则 ebx 写入内存 ; 多核环境必须加 lock 前缀保证总线/缓存行锁定 lock cmpxchg [mem], reg ; lock 前缀锁定缓存行确保操作原子性lock前缀的作用锁定缓存行阻止其他 CPU 核心同时访问该内存地址刷新 Store Buffer确保当前核心的写操作立即对其他核心可见触发缓存失效通过 MESI 协议使其他核心的缓存行失效。1.3 JVM 层面的 CAS 实现Java 通过sun.misc.Unsafe类提供 CAS 操作底层调用 JNI 映射到 CPU 指令// Unsafe 中的 CAS 方法C 实现publicfinalnativebooleancompareAndSwapInt(Objecto,longoffset,intexpected,intx);publicfinalnativebooleancompareAndSwapLong(Objecto,longoffset,longexpected,longx);publicfinalnativebooleancompareAndSwapObject(Objecto,longoffset,Objectexpected,Objectx);调用链路AtomicInteger.compareAndSet()→Unsafe.compareAndSwapInt()→JNI→lock cmpxchgx86OpenJDK 8 源码片段unsafe.cppUNSAFE_ENTRY(jboolean,Unsafe_CompareAndSwapInt(JNIEnv*env,jobject unsafe,jobject obj,jlong offset,jint e,jint x))oop pJNIHandles::resolve(obj);jint*addr(jint*)index_oop_from_field_offset_long(p,offset);return(jint)(Atomic::cmpxchg(x,addr,e))e;// 调用平台相关的 Atomic::cmpxchgUNSAFE_END2. CAS 如何保证原子性——从 CPU 到 JVM 的完整链路2.1 单指令原子性cmpxchg是一条单条 CPU 指令CPU 保证单条指令的执行不会被中断。这是 CAS 原子性的根本保障——不是通过锁而是通过操作不可拆分实现。2.2 多核环境下的缓存行锁定现代 CPU 采用缓存行Cache Line通常 64 字节作为缓存最小单位。lock cmpxchg会锁定整个缓存行确保当前核心独占该缓存行MESI 的 E/M 状态其他核心对该缓存行的访问被阻塞或延迟操作完成后通过总线嗅探通知其他核心缓存失效。2.3 与 volatile 的协同AtomicInteger内部的value字段被声明为volatileprivatevolatileintvalue;// 保证读取时从主内存加载最新值这确保了线程 A 执行 CAS 时读取到的V是主内存中的最新值而非寄存器缓存的旧值。2.4 完整执行流程步骤线程 A硬件状态说明1读取value→V0Core 0 缓存行S 状态volatile保证读到最新值2计算newValue 1—在工作内存中计算3执行lock cmpxchgCore 0 缓存行锁定为 M 状态锁定缓存行原子比较交换4比较V0→ 相等—预期值匹配5写入value 1Core 0 缓存行M 状态主内存同步更新成功6释放缓存行锁发送 Invalidate 消息Core 1 缓存行变为 I 状态3. CAS 的 ABA 问题与解决方案3.1 什么是 ABA 问题ABA 问题是指变量值从 A 变为 B再变回 ACAS 操作会误认为值没有变化从而成功更新但实际上中间过程已被其他线程修改过。示例场景时间线 T1: 线程 A 读取 value A T2: 线程 B 将 value 改为 B T3: 线程 C 将 value 改回 A T4: 线程 A 执行 CAS(A→C)比较 valueA成功更新为 C 问题线程 A 不知道中间发生过 B可能导致逻辑错误如链表头节点被篡改3.2 ABA 问题的危害ABA 在简单数值运算中通常无害0→1→0结果正确但在引用类型或复合状态场景下可能致命链表操作节点被删除后又重新插入CAS 可能操作已释放的内存状态机中间状态变化被忽略导致状态流转错误。3.3 解决方案一AtomicStampedReference版本号机制AtomicStampedReferenceIntegerstampedRefnewAtomicStampedReference(100,0);int[]stampHoldernewint[1];IntegervaluestampedRef.get(stampHolder);// 获取值和版本号intstampstampHolder[0];// 更新时同时比较值和版本号booleansuccessstampedRef.compareAndSet(value,101,stamp,stamp1);原理每次更新时递增版本号stamp即使值相同版本号不同也会失败。3.4 解决方案二AtomicMarkableReference布尔标记AtomicMarkableReferenceNodemarkableRefnewAtomicMarkableReference(head,false);// 标记节点是否被删除解决链表 ABA 问题适用场景只需区分是否被修改过不需要精确版本号。3.5 解决方案对比方案机制适用场景开销AtomicStampedReferenceint 版本号需要精确追踪修改次数额外 4 字节AtomicMarkableReferenceboolean 标记只需区分是否被修改额外 1 位Unsafe 自定义64 位拼接值版本极致性能优化无对象包装开销4. CAS 的自旋开销与优化策略4.1 自旋Spin机制CAS 失败时不会阻塞线程而是循环重试自旋直到成功// AtomicInteger.getAndAddInt 的简化逻辑publicfinalintgetAndAddInt(Objecto,longoffset,intdelta){intv;do{vgetIntVolatile(o,offset);// 读取最新值}while(!compareAndSwapInt(o,offset,v,vdelta));// CAS 失败则重试returnv;}4.2 自旋的开销问题CPU 空转自旋期间线程占用 CPU 执行无效循环缓存行竞争多个线程同时 CAS 同一变量导致缓存行频繁在核心间乒乓Cache Line Bouncing严重降低性能活锁Livelock高并发下所有线程同时读取、同时 CAS全部失败循环往复。4.3 优化策略一自适应自旋Adaptive SpinningJVM 根据历史成功率动态调整自旋次数上次自旋成功 → 增加自旋次数可能很快成功上次自旋失败 → 减少自旋次数避免浪费 CPU。JVM 参数-XX:UseSpinningJDK 6 默认开启4.3 优化策略二延迟退避Backoff失败后不立即重试而是等待随机时间降低冲突概率// 伪代码指数退避intbackoff1;while(!casSuccess){Thread.yield();// 或 sleep(backoff)backoffMath.min(backoff*2,MAX_BACKOFF);}4.4 优化策略三分段/分散热点LongAdderJava 8 引入LongAdder将单一变量拆分为多个Cell分段数组线程分散到不同 Cell 上 CAS大幅降低冲突LongAdderaddernewLongAdder();adder.increment();// 分散到不同 Cell最后 sum() 汇总性能对比100 线程并发累加 1000 万次方案耗时说明AtomicLong纯 CAS~1200ms高冲突大量自旋LongAdder分段~150ms分散热点性能提升 8 倍synchronized~800ms锁竞争上下文切换开销5. CAS 在高并发下的性能瓶颈与伪共享5.1 缓存行竞争Cache Line Bouncing当多个线程同时 CAS 同一变量时该变量所在的缓存行64 字节会在多个 CPU 核心间频繁转移导致大量总线事务Bus Traffic缓存行状态频繁在 M/S/I 间切换实际有效数据仅 4 字节int但传输 64 字节缓存行浪费带宽。5.2 伪共享False Sharing不同变量位于同一缓存行一个线程修改变量 A 会导致另一个线程的变量 B 缓存失效// ❌ 错误两个 AtomicInteger 可能在同一缓存行互相干扰publicclassFalseSharingExample{AtomicIntegercounter1newAtomicInteger(0);// 可能位于缓存行开头AtomicIntegercounter2newAtomicInteger(0);// 可能紧挨着 counter1}// 线程 A 修改 counter1 → 线程 B 的 counter2 缓存失效即使 B 没修改 counter2解决方案缓存行填充Padding// ✅ 正确使用 ContendedJDK 8或手动填充sun.misc.ContendedpublicclassPaddedAtomicInteger{volatileintvalue;// 无需手动填充Contended 自动在前后添加 128 字节填充6464}5.3 缓解策略总结瓶颈现象解决方案高冲突自旋CPU 100%吞吐量下降LongAdder 分段、延迟退避缓存行竞争总线饱和延迟飙升分散变量、缓存行对齐伪共享无关变量互相干扰Contended、缓存行填充活锁所有线程同时失败重试随机退避、指数退避6. CAS 在 JDK 中的应用场景6.1 Atomic 原子类家族类CAS 应用适用场景AtomicInteger/AtomicLong数值 CAS 更新计数器、序列号AtomicReference引用 CAS 更新无锁链表、栈AtomicStampedReference值版本号 CAS解决 ABA 问题AtomicIntegerArray/AtomicLongArray数组元素 CAS无锁数组操作6.2 AQS 框架的基石AbstractQueuedSynchronizerAQS是ReentrantLock、CountDownLatch、Semaphore的底层框架核心依赖 CAS// AQS 的核心state 变量的 CAS 操作protectedfinalbooleancompareAndSetState(intexpect,intupdate){returnunsafe.compareAndSwapInt(this,stateOffset,expect,update);}线程获取锁CAS 将state从 0 改为 1线程释放锁CAS 将state从 1 改为 0入队/出队CAS 更新head/tail指针。6.3 ConcurrentHashMapJDK 7JDK 7 的ConcurrentHashMap使用分段锁Segment CAS初始化 Segment 数组CAS 设置第一个 Segment扩容时CAS 迁移链表节点。6.4 无锁队列ConcurrentLinkedQueue完全基于 CAS 实现的无锁队列使用AtomicReference维护head/tailprivatebooleancasTail(NodeEcmp,NodeEval){returnUNSAFE.compareAndSwapObject(this,tailOffset,cmp,val);}7. 生产环境避坑指南7.1 严禁在高并发下使用 AtomicLong 做计数器100 线程并发累加时AtomicLong的 CAS 冲突率极高应优先使用LongAdder。7.2 ABA 问题不可忽视链表、栈等引用类型的无锁数据结构必须使用AtomicStampedReference纯数值运算可忽略 ABA。7.3 避免长时间自旋如果 CAS 操作可能长时间失败如高竞争场景应考虑使用Lock或synchronized避免 CPU 空转。7.4 注意 64 位变量的原子性在 32 位 JVM 中long/double的 CAS 需要拆分为两次 32 位操作应使用AtomicLongFieldUpdater或确保 64 位对齐。7.5 缓存行对齐高并发场景下使用Contended或手动填充避免伪共享尤其在计数器、统计类中。8. 面试官追问与高分回答模板追问 1“什么是 CAS底层是如何保证原子性的”低分回答“CAS 是比较并交换通过 CPU 指令保证原子性。”太浅没有触及指令和缓存高分回答CASCompare And Swap是一种乐观锁机制核心是比较内存值 V 与预期值 O相等则更新为新值 N。其原子性保障来自三个层面CPU 指令层面x86 下使用cmpxchg指令多核环境必须加lock前缀锁定缓存行确保比较和交换不可分割缓存一致性层面lock前缀触发 MESI 协议使其他核心缓存失效保证修改立即可见JVM 层面Unsafe.compareAndSwapInt()通过 JNI 调用底层原子操作且变量声明为volatile确保读取时从主内存加载最新值。关键要理解CAS 的原子性不是通过锁而是通过单条 CPU 指令不可中断 缓存行锁定实现的。追问 2“CAS 有什么缺点ABA 问题怎么解决”低分回答“有 ABA 问题用版本号解决。”没有解释危害和方案对比高分回答CAS 的主要缺点有三个ABA 问题值从 A→B→ACAS 误认为未变化。在数值运算中通常无害但在引用类型如链表头节点中可能导致操作已释放内存的严重错误。自旋开销高并发下 CAS 失败率高线程空转消耗 CPU甚至产生活锁。缓存行竞争多核同时 CAS 同一变量导致缓存行在核心间频繁乒乓总线饱和。ABA 的解决方案AtomicStampedReference使用 int 版本号每次更新递增值相同但版本不同则 CAS 失败AtomicMarkableReference使用 boolean 标记适用于只需区分是否被修改的场景自定义 64 位拼接将值和版本号拼接到一个 long 中用AtomicLong实现减少对象开销。追问 3“AtomicLong 和 LongAdder 有什么区别高并发下为什么选 LongAdder”低分回答“LongAdder 性能更好。”没有解释原理高分回答AtomicLong内部是单一volatile long变量所有线程 CAS 同一变量高并发下冲突率极高大量自旋导致 CPU 空转和缓存行竞争。LongAdder采用分段累加策略内部维护一个base值和多个CellStriped64中的数组。线程先尝试 CAS 更新base冲突严重时分散到不同Cell上各自累加最后sum()汇总所有 Cell base。这本质是将’一个热点变量’分散为’多个冷段变量’大幅降低 CAS 冲突。压测数据100 线程并发累加 1000 万次AtomicLong约 1200msLongAdder约 150ms性能提升 8 倍。但LongAdder的sum()是估算值非精确实时值如果需要精确值仍用AtomicLong。追问 4“CAS 和 synchronized 有什么区别什么时候用 CAS什么时候用锁”高分回答CAS 是乐观锁synchronized 是悲观锁核心差异冲突概率低冲突用 CAS无阻塞、无上下文切换高冲突用 synchronized避免 CPU 空转操作复杂度简单原子操作计数、累加用 CAS复杂临界区多变量联动用 synchronized阻塞需求需要阻塞等待如生产者-消费者队列满时阻塞用 Lock/synchronized纯无锁场景用 CAS公平性CAS 天然非公平synchronized 和 ReentrantLock 可配置公平性。决策原则先评估冲突概率——低冲突10 线程竞争同一变量选 CAS高冲突选 LongAdder 或锁。追问 5“AQS 为什么用 CAS 而不是 synchronized”高分回答AQS 使用 CAS 有三个核心原因性能锁的获取和释放是最高频操作CAS 无锁避免了内核态上下文切换低竞争下性能碾压 synchronized灵活性AQS 需要同时管理多个状态state、head、tailCAS 可以精确控制每个变量的原子更新而 synchronized 是粗粒度锁可扩展性AQS 支持共享锁和独占锁CAS 可以灵活实现acquireShared/releaseShared等复杂逻辑。但 AQS 并非完全无锁当 CAS 失败多次后线程会进入park阻塞LockSupport.park()此时退化为阻塞锁。所以 AQS 是’先 CAS 乐观尝试失败再阻塞’的混合策略。追问 6“在 32 位 JVM 中64 位变量的 CAS 有什么问题”高分回答32 位 JVM 中long和double的读写会被拆分为两次 32 位操作非原子。Unsafe.compareAndSwapLong()在 32 位系统上需要特殊处理对齐要求64 位变量必须 8 字节对齐否则 CAS 可能跨越两个缓存行导致非原子实现方式部分 JVM 实现使用cmpxchg8b指令64 位比较交换但需确保操作期间不被中断最佳实践32 位系统尽量避免 64 位 CAS或使用AtomicLongFieldUpdater确保字段对齐。9. 方案选型速查表业务场景推荐方案核心理由简单计数器低并发AtomicInteger/AtomicLongCAS 无锁性能最优高并发计数器/统计累加LongAdder/DoubleAdder分段累加避免 CAS 热点需要精确实时值AtomicLongLongAdder.sum()是估算值解决 ABA 问题引用类型AtomicStampedReference版本号机制精确追踪无锁链表/栈AtomicReferenceAtomicStampedReferenceCAS 更新头节点版本号防 ABA复杂临界区多变量synchronized/ReentrantLock保证互斥性和原子性高冲突且需阻塞ReentrantLock避免 CAS 空转支持条件变量缓存行对齐优化ContendedAtomicLong避免伪共享提升多核性能面试官想要的满分总结CAS 是 Java 并发编程的基石其原子性不是通过锁而是通过单条 CPU 原子指令cmpxchg/ldrex-strex 缓存行锁定 MESI 缓存一致性协议实现的。理解 CAS 必须抓住三个关键点硬件层面lock cmpxchg锁定缓存行确保比较和交换不可分割JVM 层面Unsafe通过 JNI 映射到 CPU 指令volatile保证读取最新值应用层面低冲突场景 CAS 性能无敌高冲突场景必须用LongAdder分段或退化为锁。ABA 问题在引用类型中不可忽视AtomicStampedReference是标准解法。高并发计数器首选LongAdder其分段累加策略将热点分散性能碾压AtomicLong。最后记住CAS 不是银弹高冲突下的自旋和缓存行竞争可能比锁更慢正确选型需要结合冲突概率、操作复杂度和实时性要求综合判断。觉得对您有帮助麻烦点点关注啦您的关注是我创作的最大动力~