什么是 CAS?有什么优缺点?

401 阅读3分钟

CAS(Compare and Swap,比较并交换) 是一种无锁的原子操作机制,它通过硬件指令(如 x86 的 cmpxchg)实现,核心是在不使用锁的情况下,保证多线程环境下对共享变量操作的原子性。

一、CAS 的核心原理

CAS 操作包含三个关键参数:内存地址(V)、预期值(A) 和 新值(B)。其执行逻辑如下:

1.比较内存地址 V 中的当前值是否等于预期值 A2.如果相等,将内存地址 V 中的值更新为新值 B3.如果不相等,不做任何操作。

4.无论操作成功与否,都返回内存地址 V 中的旧值。

整个过程是原子性的,由 CPU 硬件指令保证,不会被其他线程打断。

Java 中的实现:Java 通过 sun.misc.Unsafe 类(底层调用 JNI 方法)直接调用硬件指令实现 CAS,典型应用是 java.util.concurrent.atomic 包下的原子类(如 AtomicInteger)。

示例(AtomicInteger 的自增逻辑):

public final int incrementAndGet() {
    for (;;) { // 自旋重试
        int current = get();
        int next = current + 1;
        // CAS 操作:若当前值等于 current,则更新为 next
        if (compareAndSet(current, next)) {
            return next;
        }
    }
}

二、CAS 的优点

1.无锁开销:避免了传统锁(如 synchronized)的上下文切换、线程阻塞/唤醒等开销,在并发竞争不激烈时,性能优于锁机制。

2.并发安全性:通过硬件原子指令保证操作的原子性,确保多线程环境下数据一致性。

3.灵活性:可根据业务场景自定义重试逻辑(如自旋次数限制),比锁机制更灵活。

三、CAS 的缺点

1.ABA 问题

问题描述:变量 V 的值从 A 被改为 B,又被改回 A,CAS 操作会误认为“值未被修改”而成功更新,但实际中间已被篡改。

解决方式:使用“版本号”或“时间戳”标记变量(如 Java 中的 AtomicStampedReference,在 CAS 时同时比较值和版本号)。

2.自旋重试开销

问题描述:当并发竞争激烈时,CAS 会频繁失败并进入自旋重试(如示例中的 for (;;) 循环),导致 CPU 资源被大量占用。

缓解方式:限制自旋次数、引入自适应自旋(根据历史重试情况动态调整自旋次数),或在竞争激烈时降级为锁。

3.只能保证单个变量的原子性

CAS 仅能对单个共享变量的操作实现原子性,无法直接保证多个变量操作的原子性(如同时更新两个变量)。

解决方式:可将多个变量封装为一个对象(如自定义一个类),通过 AtomicReference 对对象进行 CAS 操作。

4.无法感知变量的变化

CAS 是“乐观锁”思想,仅在更新时检查值是否变化,无法主动感知其他线程对变量的修改,不适用于需要“等待变量变化”的场景(此时需用 Condition 等工具)。

总结

CAS 是一种高效的无锁原子操作机制,适用于并发竞争不激烈的场景(如计数器、ID 生成器)。但需注意解决 ABA 问题、控制自旋开销,并明确其仅支持单个变量原子操作的局限性。在并发竞争激烈时,传统锁(如 synchronized,JDK 1.6 后已优化)可能更合适。