CAS 无锁并发深度解析:从 CPU 原语、JDK 源码到生产实战与避坑指南

张开发
2026/4/3 14:28:58 15 分钟阅读
CAS 无锁并发深度解析:从 CPU 原语、JDK 源码到生产实战与避坑指南
前言在Java高并发编程中CAS是无锁并发的核心基石也是AQS、原子类、ConcurrentHashMap等JUC核心组件的底层依赖。多数开发者仅停留在AtomicInteger的API使用层面对其底层实现、原子性保障原理一知半解生产环境中频繁踩中ABA、自旋CPU飙升、伪共享等致命坑。本文从CPU原语、JDK17源码、生产实战、踩坑避坑全链路拆解CAS帮你彻底吃透无锁并发的核心逻辑。一、CAS基础认知与核心背景1.1 什么是CASCAS全称Compare-And-Swap比较并交换是一种硬件级别的原子操作原语核心语义是针对内存地址V给定旧预期值A与新值B当且仅当V的当前值等于A时才将V的值原子更新为B整个操作不可中断。它是乐观锁的核心实现区别于synchronized等悲观锁的“先加锁再操作”CAS采用“先验证再更新”的无锁思路在低并发场景下大幅降低线程调度与上下文切换的开销。1.2 核心前置知识铺垫要彻底理解CAS必须先明确两个核心基础CPU原子操作保障现代多核CPU通过缓存一致性协议如MESI、总线锁/缓存行锁保障单个内存操作的原子性。JMM内存模型CAS操作同时具备volatile的读写内存语义保证变量的可见性与禁止指令重排序解决多线程下的内存不可见问题。二、CAS底层核心原理解析JDK 17源码2.1 CPU层面的原子性实现CAS的原子性本质是CPU硬件层面的指令支持不同CPU架构有不同实现以主流X86_64架构为例 CAS的核心是CMPXCHG指令比较并交换指令但该指令本身不具备多核原子性必须搭配LOCK前缀才能实现多核环境下的原子操作。LOCK前缀的核心作用锁定操作对应的内存地址的缓存行基于MESI协议避免多核CPU同时修改该内存地址禁止该指令与前后的读写指令重排序刷新写缓冲区保证操作结果对所有CPU核心立即可见。极端情况下操作数据跨缓存行LOCK前缀会降级为锁总线保证操作的原子性但性能开销会显著提升。2.2 JDK中CAS的核心载体Unsafe类Java作为高级语言无法直接操作内存地址CAS操作完全依赖jdk.internal.misc.Unsafe类JDK9后从sun.misc.Unsafe迁移的native方法实现。Unsafe类中CAS的核心方法定义JDK 17// 针对int类型的CAS操作 public final native boolean compareAndSetInt(Object o, long offset, int expected, int x); // 针对long类型的CAS操作 public final native boolean compareAndSetLong(Object o, long offset, long expected, long x); // 针对引用类型的CAS操作 public final native boolean compareAndSetReference(Object o, long offset, Object expected, Object x);方法参数说明o目标对象offset目标字段在对象中的内存偏移量固定值类加载时计算expected旧预期值x待更新的新值 返回值boolean类型true表示更新成功false表示更新失败。2.3 原子类的CAS源码拆解AtomicInteger为例JUC中的原子类是CAS最典型的应用我们以JDK 17的AtomicInteger为例拆解其底层CAS实现public class AtomicInteger extends Number implements java.io.Serializable { private static final jdk.internal.misc.Unsafe U jdk.internal.misc.Unsafe.getUnsafe(); // 计算value字段的内存偏移量 private static final long VALUE U.objectFieldOffset(AtomicInteger.class, value); // 核心value字段volatile修饰保证可见性 private volatile int value; // 原子递增i的原子实现 public final int getAndIncrement() { return U.getAndAddInt(this, VALUE, 1); } }我们继续看getAndAddInt方法的核心逻辑JDK 17中该方法为内置方法等价于如下实现public final int getAndAddInt(Object o, long offset, int delta) { int oldValue; do { // 循环获取内存中的最新值 oldValue this.getIntVolatile(o, offset); // CAS尝试更新如果当前值等于oldValue就更新为oldValuedelta } while (!this.compareAndSetInt(o, offset, oldValue, oldValue delta)); // 返回更新前的旧值 return oldValue; }这里的核心就是自旋CAS如果CAS更新失败说明当前值被其他线程修改重新获取最新值再次尝试直到更新成功。2.4 JDK 17 推荐替代方案VarHandleJDK 9之后引入了VarHandle变量句柄作为Unsafe类的合法替代方案解决了Unsafe类的类型不安全、权限不受控、模块化不兼容的问题。VarHandle同样支持CAS操作且性能与Unsafe相当JDK 17中JUC源码已大量迁移至VarHandle实现。核心示例如下public class VarHandleCasDemo { private volatile int value; // 定义VarHandle实例 private static final VarHandle VALUE_HANDLE; static { try { // 初始化VarHandle绑定value字段 VALUE_HANDLE MethodHandles.lookup() .findVarHandle(VarHandleCasDemo.class, value, int.class); } catch (ReflectiveOperationException e) { throw new ExceptionInInitializerError(e); } } // 基于VarHandle实现CAS原子递增 public final int getAndIncrement() { int oldValue; do { oldValue (int) VALUE_HANDLE.getVolatile(this); } while (!VALUE_HANDLE.compareAndSet(this, oldValue, oldValue 1)); return oldValue; } }三、CAS生产实战落地JDK 17我们基于真实业务场景实现3个高复用的CAS实战案例代码严格遵循《阿里巴巴Java开发手册》规范。3.1 实战场景1基于CAS实现无锁并发计数器业务场景高并发场景下的接口QPS统计、订单量统计对比synchronized锁与CAS无锁计数器的性能差异。 完整代码实现import lombok.extern.slf4j.Slf4j; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * 基于CAS实现的无锁并发计数器 * author 果酱 */ Slf4j public class CasLockFreeCounter { private volatile int count; private static final jdk.internal.misc.Unsafe U jdk.internal.misc.Unsafe.getUnsafe(); private static final long COUNT_OFFSET; static { try { COUNT_OFFSET U.objectFieldOffset(CasLockFreeCounter.class, count); } catch (Exception e) { throw new Error(e); } } /** * 原子递增计数 * return 递增后的最新值 */ public int increment() { int oldCount; int newCount; do { oldCount U.getIntVolatile(this, COUNT_OFFSET); newCount oldCount 1; // 自旋CAS更新 } while (!U.compareAndSetInt(this, COUNT_OFFSET, oldCount, newCount)); return newCount; } /** * 获取当前计数值 * return 当前计数 */ public int getCount() { return U.getIntVolatile(this, COUNT_OFFSET); } // 性能测试对比 public static void main(String[] args) throws InterruptedException { int threadNum 100; int incrementTimes 10000; ExecutorService executor Executors.newFixedThreadPool(threadNum); CountDownLatch countDownLatch new CountDownLatch(threadNum); CasLockFreeCounter counter new CasLockFreeCounter(); long startTime System.currentTimeMillis(); for (int i 0; i threadNum; i) { executor.submit(() - { try { for (int j 0; j incrementTimes; j) { counter.increment(); } } finally { countDownLatch.countDown(); } }); } countDownLatch.await(); long endTime System.currentTimeMillis(); log.info(CAS无锁计数器最终结果{}, counter.getCount()); log.info(CAS无锁计数器耗时{}ms, endTime - startTime); executor.shutdown(); } }注意事项JDK 17中运行该代码需要添加JVM启动参数--add-opens java.base/jdk.internal.miscALL-UNNAMED否则会抛出权限异常生产环境推荐使用VarHandle替代Unsafe。3.2 实战场景2基于CAS实现可重入自旋锁业务场景分布式锁的本地自旋优化、短执行时间的临界区资源保护避免线程上下文切换开销。 完整代码实现import lombok.extern.slf4j.Slf4j; import java.util.concurrent.atomic.AtomicReference; /** * 基于CAS实现的可重入自旋锁 * author 果酱 */ Slf4j public class CasReentrantSpinLock { // 持有锁的线程引用 private final AtomicReferenceThread lockHolder new AtomicReference(); // 重入计数 private volatile int holdCount 0; /** * 加锁自旋CAS获取锁 */ public void lock() { Thread currentThread Thread.currentThread(); // 重入判断当前线程已持有锁直接计数1 if (currentThread lockHolder.get()) { holdCount; return; } // 自旋CAS获取锁 while (!lockHolder.compareAndSet(null, currentThread)) { // 自旋等待空循环 } holdCount 1; } /** * 解锁CAS释放锁 */ public void unlock() { Thread currentThread Thread.currentThread(); // 只有持有锁的线程才能解锁 if (currentThread ! lockHolder.get()) { throw new IllegalMonitorStateException(当前线程未持有该锁无法解锁); } holdCount--; // 重入计数为0时真正释放锁 if (holdCount 0) { lockHolder.compareAndSet(currentThread, null); } } // 测试验证 public static void main(String[] args) throws InterruptedException { CasReentrantSpinLock spinLock new CasReentrantSpinLock(); int[] count {0}; int threadNum 10; int loopTimes 10000; Thread[] threads new Thread[threadNum]; for (int i 0; i threadNum; i) { threads[i] new Thread(() - { for (int j 0; j loopTimes; j) { spinLock.lock(); try { // 重入测试 spinLock.lock(); count[0]; } finally { spinLock.unlock(); spinLock.unlock(); } } }); } long startTime System.currentTimeMillis(); for (Thread thread : threads) { thread.start(); } for (Thread thread : threads) { thread.join(); } long endTime System.currentTimeMillis(); log.info(自旋锁最终计数结果{}, count[0]); log.info(自旋锁执行耗时{}ms, endTime - startTime); } }3.3 实战场景3基于CAS解决ABA问题的版本号控制业务场景账户余额修改、库存扣减等需要严格校验数据版本的场景避免ABA问题导致的业务数据错误。 完整代码实现import lombok.extern.slf4j.Slf4j; import java.util.concurrent.atomic.AtomicStampedReference; /** * 基于AtomicStampedReference解决ABA问题的账户余额操作 * author 果酱 */ Slf4j public class CasAbaSolutionDemo { // 账户余额带版本号的原子引用解决ABA问题 private final AtomicStampedReferenceInteger accountBalance; public CasAbaSolutionDemo(Integer initBalance) { // 初始化余额与初始版本号 this.accountBalance new AtomicStampedReference(initBalance, 0); } /** * 账户余额扣减 * param amount 扣减金额 * return 扣减是否成功 */ public boolean deductBalance(Integer amount) { if (amount 0) { log.error(扣减金额必须大于0当前金额{}, amount); return false; } int[] stampHolder new int[1]; // 获取当前余额与当前版本号 Integer currentBalance accountBalance.get(stampHolder); int currentStamp stampHolder[0]; if (currentBalance amount) { log.error(账户余额不足当前余额{}扣减金额{}, currentBalance, amount); return false; } // CAS更新必须同时匹配余额与版本号版本号1 boolean result accountBalance.compareAndSet( currentBalance, currentBalance - amount, currentStamp, currentStamp 1 ); if (result) { log.info(余额扣减成功扣减金额{}当前余额{}最新版本号{}, amount, accountBalance.getReference(), accountBalance.getStamp()); } else { log.warn(余额扣减失败数据已被其他线程修改当前版本号{}, currentStamp); } return result; } // 测试ABA场景 public static void main(String[] args) throws InterruptedException { CasAbaSolutionDemo account new CasAbaSolutionDemo(1000); // 线程1先扣减500再充值500制造ABA场景 Thread thread1 new Thread(() - { account.deductBalance(500); // 模拟充值 int[] stampHolder new int[1]; Integer balance account.accountBalance.get(stampHolder); account.accountBalance.compareAndSet(balance, balance 500, stampHolder[0], stampHolder[0] 1); log.info(线程1完成ABA操作当前余额{}版本号{}, account.accountBalance.getReference(), account.accountBalance.getStamp()); }); // 线程2基于版本号校验扣减避免ABA问题 Thread thread2 new Thread(() - { try { // 等待线程1完成ABA操作 Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } account.deductBalance(800); }); thread1.start(); thread2.start(); thread1.join(); thread2.join(); } }四、CAS生产踩坑指南与避坑最佳实践4.1 坑1ABA问题导致业务数据异常问题现象线程1读取变量值为A此时线程2将变量值从A改为B再改回A线程1执行CAS时发现值还是A认为数据未被修改执行更新成功导致业务逻辑错误。典型场景包括库存扣减、账户余额修改、链表结构修改等ABA问题会导致数据丢失、业务状态异常。根因分析CAS仅校验变量的当前值未校验变量的变更过程无法识别数据是否被修改过。避坑方案版本号控制使用AtomicStampedReference给变量附加一个递增的版本号CAS操作必须同时匹配值与版本号只要数据被修改过版本号就会递增彻底解决ABA问题。标记位控制对于仅需判断是否被修改过的场景使用AtomicMarkableReference附加一个boolean类型的标记位记录变量是否被修改过。业务场景约束对于单调递增/递减的场景如自增ID、流水号ABA问题不会产生业务影响无需额外处理。4.2 坑2高并发下自旋过度导致CPU 100%飙升问题现象高并发场景下使用CAS实现的计数器、自旋锁出现CPU使用率持续100%业务接口响应超时。根因分析CAS更新失败时会进入自旋循环高并发下大量线程同时竞争同一个变量CAS更新成功率极低导致线程持续占用CPU自旋耗尽CPU资源。避坑方案限定自旋次数给自旋循环设置最大次数阈值超过阈值后升级为悲观锁如synchronized避免无限自旋。自适应自旋参考JVM synchronized的自适应自旋优化根据前一次自旋的成功情况动态调整当前自旋次数。分段CAS优化使用JDK提供的LongAdder/DoubleAdder替代AtomicLong通过分段数组分散竞争将单点CAS竞争分散到多个Cell元素大幅提升高并发下的性能。放弃自旋使用阻塞机制对于长执行时间的临界区直接使用悲观锁避免自旋带来的CPU开销。4.3 坑3伪共享问题导致CAS性能急剧下降问题现象多个CAS操作的变量位于同一个CPU缓存行中多线程同时修改这些变量时频繁出现缓存行失效CAS性能下降数十倍。根因分析CPU缓存的最小单位是缓存行通常64字节当多个变量位于同一个缓存行时一个变量的修改会导致整个缓存行失效其他CPU核心需要重新从主内存加载数据即伪共享问题。CAS操作频繁修改变量会放大伪共享的性能影响。避坑方案缓存行填充在变量前后填充7个long类型变量8字节*864字节让单个变量独占一个缓存行。使用Contended注解JDK 1.8提供的sun.misc.Contended注解自动实现缓存行填充JDK 17中需要添加JVM启动参数-XX:-RestrictContended才能生效。变量隔离将频繁修改的CAS变量与其他变量分开存储避免同处一个缓存行。4.4 坑464位long/double变量的CAS非原子性问题问题现象32位JVM/操作系统中对long/double类型变量执行CAS操作时出现更新异常原子性无法保障。根因分析JVM规范中64位的long/double类型变量的读写操作分为两个32位的操作执行不保证原子性。32位环境下CAS操作无法保障64位变量的单次读写原子性导致CAS校验失败。避坑方案volatile修饰给long/double类型变量添加volatile修饰符JVM会保证64位变量的读写操作的原子性。使用封装原子类直接使用AtomicLong/AtomicDouble底层已处理原子性问题。64位环境优先生产环境优先使用64位JVM与操作系统从根本上避免该问题。4.5 坑5多字段原子更新的CAS误用问题现象需要同时更新多个字段时分别对每个字段执行CAS操作导致原子性无法保障出现数据不一致问题。根因分析单次CAS操作只能保证单个变量的原子性多个CAS操作无法构成原子操作中间可能被其他线程打断导致部分字段更新成功部分更新失败。避坑方案对象封装将多个需要原子更新的字段封装成一个不可变对象使用AtomicReference对整个对象执行CAS操作保证多字段更新的原子性。加锁保障对于多字段复杂更新场景直接使用悲观锁避免CAS误用带来的数据不一致问题。五、CAS性能优化与进阶拓展5.1 CAS的适用场景边界CAS不是银弹必须明确其适用场景才能发挥最大性能优势场景类型推荐方案核心原因低并发、短执行时间临界区CAS无锁方案无线程上下文切换开销性能远超悲观锁高并发、热点变量竞争LongAdder分段CAS分散竞争避免单点自旋性能提升10倍以上长执行时间临界区、复杂业务逻辑synchronized/Lock悲观锁避免长时间自旋耗尽CPU资源多字段原子更新悲观锁/AtomicReference封装保证原子性避免数据不一致5.2 JDK中CAS的核心应用场景CAS是整个JUC并发包的基石JDK中大量核心组件都基于CAS实现原子类AtomicInteger、AtomicLong等原子类全部基于自旋CAS实现原子操作。AQS抽象队列同步器ReentrantLock、CountDownLatch、Semaphore等同步工具基于CAS实现同步状态的原子更新、CLH队列的节点入队出队。ConcurrentHashMapJDK 1.8的ConcurrentHashMap基于CAS实现数组元素的原子更新替代了分段锁大幅提升并发性能。线程池ThreadPoolExecutor基于CAS实现线程池状态的原子更新、工作线程的计数。LockSupport配合CAS实现线程的阻塞与唤醒是AQS的底层依赖。5.3 CAS进阶优化方向无锁并发数据结构基于CAS实现无锁队列、无锁栈、无锁哈希表等数据结构避免锁开销提升高并发下的吞吐量。混合锁机制结合CAS与悲观锁短时间内使用CAS自旋超过阈值后升级为阻塞锁兼顾低延迟与高并发稳定性。硬件级优化利用CPU的TSX事务同步扩展指令实现硬件级别的事务内存优化CAS的竞争开销。分布式CAS基于Redis的CAS操作SETNX、WATCHMULTI、分布式锁的自旋优化实现分布式环境下的无锁并发控制。全文总结本文从CPU原语、JDK 17源码、生产实战、踩坑避坑全链路深度拆解了CAS无锁并发的核心逻辑。 核心要点总结CAS是硬件级别的原子操作原语X86架构下基于LOCKCMPXCHG指令实现多核原子性是Java无锁并发的核心基石。JDK中CAS的核心载体是Unsafe类JDK 9推荐使用类型安全的VarHandle作为替代方案。CAS的核心优势是无锁、低延迟核心缺陷是ABA问题、自旋CPU开销、伪共享、单变量原子性限制。生产环境中必须根据业务场景选择合适的方案高并发热点变量优先使用LongAdder需要版本校验的场景使用AtomicStampedReference避免踩中常见坑。CAS是Java并发编程的核心基本功只有彻底吃透其底层原理与边界才能在生产环境中写出高性能、高稳定的并发代码。后续我们会继续拆解AQS、ConcurrentHashMap等JUC核心组件的CAS实现欢迎持续关注。有关果酱作者果酱 专注Java核心技术、分布式架构、性能优化与生产实战。 本文原创首发于阿里云公众号CSDN稀土掘金未经授权禁止任何形式的转载、抄袭与洗稿。觉得文章有帮助的同学欢迎点赞 收藏⭐ 关注✅。

更多文章