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

0 阅读15分钟

## 前言

在Java高并发编程中,CAS是无锁并发的核心基石,也是AQS、原子类、ConcurrentHashMap等JUC核心组件的底层依赖。多数开发者仅停留在AtomicInteger的API使用层面,对其底层实现、原子性保障原理一知半解,生产环境中频繁踩中ABA、自旋CPU飙升、伪共享等致命坑。本文从CPU原语、JDK17源码、生产实战、踩坑避坑全链路拆解CAS,帮你彻底吃透无锁并发的核心逻辑。

一、CAS基础认知与核心背景

1.1 什么是CAS

CAS全称Compare-And-Swap(比较并交换),是一种硬件级别的原子操作原语,核心语义是:针对内存地址V,给定旧预期值A与新值B,当且仅当V的当前值等于A时,才将V的值原子更新为B,整个操作不可中断。

它是乐观锁的核心实现,区别于synchronized等悲观锁的“先加锁再操作”,CAS采用“先验证再更新”的无锁思路,在低并发场景下大幅降低线程调度与上下文切换的开销。

1.2 核心前置知识铺垫

要彻底理解CAS,必须先明确两个核心基础:

  1. CPU原子操作保障:现代多核CPU通过缓存一致性协议(如MESI)、总线锁/缓存行锁,保障单个内存操作的原子性。
  2. 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,就更新为oldValue+delta
    } while (!this.compareAndSetInt(o, offset, oldValue, oldValue + delta));
    // 返回更新前的旧值
    return oldValue;
}

这里的核心就是自旋CAS:如果CAS更新失败,说明当前值被其他线程修改,重新获取最新值再次尝试,直到更新成功。

2.4 JDK 17+ 推荐替代方案:VarHandle

JDK 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.misc=ALL-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 AtomicReference<Thread> 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 AtomicStampedReference<Integer> 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 坑1:ABA问题导致业务数据异常

问题现象:线程1读取变量值为A,此时线程2将变量值从A改为B,再改回A,线程1执行CAS时发现值还是A,认为数据未被修改,执行更新成功,导致业务逻辑错误。典型场景包括库存扣减、账户余额修改、链表结构修改等,ABA问题会导致数据丢失、业务状态异常。 根因分析:CAS仅校验变量的当前值,未校验变量的变更过程,无法识别数据是否被修改过。 避坑方案

  1. 版本号控制:使用AtomicStampedReference,给变量附加一个递增的版本号,CAS操作必须同时匹配值与版本号,只要数据被修改过,版本号就会递增,彻底解决ABA问题。
  2. 标记位控制:对于仅需判断是否被修改过的场景,使用AtomicMarkableReference,附加一个boolean类型的标记位,记录变量是否被修改过。
  3. 业务场景约束:对于单调递增/递减的场景(如自增ID、流水号),ABA问题不会产生业务影响,无需额外处理。

4.2 坑2:高并发下自旋过度导致CPU 100%飙升

问题现象:高并发场景下,使用CAS实现的计数器、自旋锁出现CPU使用率持续100%,业务接口响应超时。 根因分析:CAS更新失败时会进入自旋循环,高并发下大量线程同时竞争同一个变量,CAS更新成功率极低,导致线程持续占用CPU自旋,耗尽CPU资源。 避坑方案

  1. 限定自旋次数:给自旋循环设置最大次数阈值,超过阈值后升级为悲观锁(如synchronized),避免无限自旋。
  2. 自适应自旋:参考JVM synchronized的自适应自旋优化,根据前一次自旋的成功情况,动态调整当前自旋次数。
  3. 分段CAS优化:使用JDK提供的LongAdder/DoubleAdder替代AtomicLong,通过分段数组分散竞争,将单点CAS竞争分散到多个Cell元素,大幅提升高并发下的性能。
  4. 放弃自旋,使用阻塞机制:对于长执行时间的临界区,直接使用悲观锁,避免自旋带来的CPU开销。

4.3 坑3:伪共享问题导致CAS性能急剧下降

问题现象:多个CAS操作的变量位于同一个CPU缓存行中,多线程同时修改这些变量时,频繁出现缓存行失效,CAS性能下降数十倍。 根因分析:CPU缓存的最小单位是缓存行(通常64字节),当多个变量位于同一个缓存行时,一个变量的修改会导致整个缓存行失效,其他CPU核心需要重新从主内存加载数据,即伪共享问题。CAS操作频繁修改变量,会放大伪共享的性能影响。 避坑方案

  1. 缓存行填充:在变量前后填充7个long类型变量(8字节*8=64字节),让单个变量独占一个缓存行。
  2. 使用@Contended注解:JDK 1.8+提供的@sun.misc.Contended注解,自动实现缓存行填充,JDK 17中需要添加JVM启动参数-XX:-RestrictContended才能生效。
  3. 变量隔离:将频繁修改的CAS变量与其他变量分开存储,避免同处一个缓存行。

4.4 坑4:64位long/double变量的CAS非原子性问题

问题现象:32位JVM/操作系统中,对long/double类型变量执行CAS操作时,出现更新异常,原子性无法保障。 根因分析:JVM规范中,64位的long/double类型变量的读写操作分为两个32位的操作执行,不保证原子性。32位环境下,CAS操作无法保障64位变量的单次读写原子性,导致CAS校验失败。 避坑方案

  1. volatile修饰:给long/double类型变量添加volatile修饰符,JVM会保证64位变量的读写操作的原子性。
  2. 使用封装原子类:直接使用AtomicLong/AtomicDouble,底层已处理原子性问题。
  3. 64位环境优先:生产环境优先使用64位JVM与操作系统,从根本上避免该问题。

4.5 坑5:多字段原子更新的CAS误用

问题现象:需要同时更新多个字段时,分别对每个字段执行CAS操作,导致原子性无法保障,出现数据不一致问题。 根因分析:单次CAS操作只能保证单个变量的原子性,多个CAS操作无法构成原子操作,中间可能被其他线程打断,导致部分字段更新成功,部分更新失败。 避坑方案

  1. 对象封装:将多个需要原子更新的字段封装成一个不可变对象,使用AtomicReference对整个对象执行CAS操作,保证多字段更新的原子性。
  2. 加锁保障:对于多字段复杂更新场景,直接使用悲观锁,避免CAS误用带来的数据不一致问题。

五、CAS性能优化与进阶拓展

5.1 CAS的适用场景边界

CAS不是银弹,必须明确其适用场景,才能发挥最大性能优势:

场景类型推荐方案核心原因
低并发、短执行时间临界区CAS无锁方案无线程上下文切换开销,性能远超悲观锁
高并发、热点变量竞争LongAdder分段CAS分散竞争,避免单点自旋,性能提升10倍以上
长执行时间临界区、复杂业务逻辑synchronized/Lock悲观锁避免长时间自旋耗尽CPU资源
多字段原子更新悲观锁/AtomicReference封装保证原子性,避免数据不一致

5.2 JDK中CAS的核心应用场景

CAS是整个JUC并发包的基石,JDK中大量核心组件都基于CAS实现:

  1. 原子类:AtomicInteger、AtomicLong等原子类,全部基于自旋CAS实现原子操作。
  2. AQS抽象队列同步器:ReentrantLock、CountDownLatch、Semaphore等同步工具,基于CAS实现同步状态的原子更新、CLH队列的节点入队出队。
  3. ConcurrentHashMap:JDK 1.8+的ConcurrentHashMap,基于CAS实现数组元素的原子更新,替代了分段锁,大幅提升并发性能。
  4. 线程池:ThreadPoolExecutor基于CAS实现线程池状态的原子更新、工作线程的计数。
  5. LockSupport:配合CAS实现线程的阻塞与唤醒,是AQS的底层依赖。

5.3 CAS进阶优化方向

  1. 无锁并发数据结构:基于CAS实现无锁队列、无锁栈、无锁哈希表等数据结构,避免锁开销,提升高并发下的吞吐量。
  2. 混合锁机制:结合CAS与悲观锁,短时间内使用CAS自旋,超过阈值后升级为阻塞锁,兼顾低延迟与高并发稳定性。
  3. 硬件级优化:利用CPU的TSX(事务同步扩展)指令,实现硬件级别的事务内存,优化CAS的竞争开销。
  4. 分布式CAS:基于Redis的CAS操作(SETNX、WATCH+MULTI)、分布式锁的自旋优化,实现分布式环境下的无锁并发控制。

全文总结

本文从CPU原语、JDK 17源码、生产实战、踩坑避坑全链路,深度拆解了CAS无锁并发的核心逻辑。 核心要点总结:

  1. CAS是硬件级别的原子操作原语,X86架构下基于LOCK+CMPXCHG指令实现多核原子性,是Java无锁并发的核心基石。
  2. JDK中CAS的核心载体是Unsafe类,JDK 9+推荐使用类型安全的VarHandle作为替代方案。
  3. CAS的核心优势是无锁、低延迟,核心缺陷是ABA问题、自旋CPU开销、伪共享、单变量原子性限制。
  4. 生产环境中,必须根据业务场景选择合适的方案,高并发热点变量优先使用LongAdder,需要版本校验的场景使用AtomicStampedReference,避免踩中常见坑。

CAS是Java并发编程的核心基本功,只有彻底吃透其底层原理与边界,才能在生产环境中写出高性能、高稳定的并发代码。后续我们会继续拆解AQS、ConcurrentHashMap等JUC核心组件的CAS实现,欢迎持续关注。


有关果酱

作者:果酱 专注Java核心技术、分布式架构、性能优化与生产实战。 本文原创首发于阿里云,公众号,CSDN,稀土掘金,未经授权禁止任何形式的转载、抄袭与洗稿。觉得文章有帮助的同学,欢迎点赞👍 收藏⭐ 关注✅。