定义


CAS

CAS(Compare-And-Swap)是一种无锁的原子算法,是乐观锁的核心实现机制。
它是CPU提供的一条原子指令,包含3个基本操作数:

  1. V:需要读写的内存位置。
  2. A:预期的旧值。
  3. B:拟写入的新值。

Java实现


Unsafe

Unsafe中存在一系列native方法,能够直接操作内存和调用CPU指令。

public final class Unsafe {
 
	public final native boolean compareAndSwapInt(Object o, long offset,
                                                  int expected,
                                                  int x);
                                                  
	public final int getAndAddInt(Object o, long offset, int delta) {
        int v;
        do {
            v = getIntVolatile(o, offset); // 拿到volatile变量最新值
        } while (!compareAndSwapInt(o, offset, v, v + delta)); // CAS失败时不断循环重试
        return v;
    }
}

AtomicInteger(JDK 8)

相关原子类会调用Unsafe中的方法。

public class AtomicInteger extends Number implements Serializable {
 
    // setup to use Unsafe.compareAndSwapInt for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset; // 变量在内存中的绝对偏移地址
 
    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }
 
    private volatile int value; // volatile保证内存可见性
    
    public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }
}

在 JDK 9 以后,官方推荐使用 VarHandle 逐步替代直接使用 Unsafe,其底层原理依然是 CAS。

硬件实现


C++实现

在x86平台上,虚拟机相关代码会调用系统cmpxchg(Compare and Exchange)指令。hotspot/src/os_cpu/linux_x86/vm/atomic_linux_x86.inline.hpp:

inline jint     Atomic::cmpxchg    (jint     exchange_value, volatile jint*     dest, jint     compare_value) {
  int mp = os::is_MP(); // 是否为多核处理器(Multi-Processor)
  __asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)"
                    : "=a" (exchange_value)
                    : "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
                    : "cc", "memory");
  return exchange_value;
}

汇编实现

在 x86 架构下,最终的汇编指令为

lock cmpxchg [edx], ecx

cmpxchg是CPU原生提供的比较并交换指令。
对于多核CPU,会在指令前加上lock。
lock的作用:

  1. 早期CPU:锁住总线,令其他CPU核心都无法访问内存。
  2. 现代CPU:通过MESI缓存一致性协议锁缓存行,其他核心访问该缓存行会被阻塞。
  3. 内存屏障:防止指令前后的读写指令重排序。

CAS的三大问题


ABA问题

线程1读取变量为A,进行处理,此时线程2将变量修改为B又修改回A,当线程1进行CAS时,变量仍然为A,于是成功执行。但变量已经经过修改,可能不符合原来业务的语义,在指针引用、链表和栈相关结构中可能出现问题。
解决方案:

  • 版本号机制:每次更新时不仅比较值,还要比较版本号。
    • AtomicStampedReference:同时维护引用和整数版本号,同时对比值与整数版本号。
    • AtomicMarkableReference:与AtomicStampedReference相似,但版本号改为一个boolean标记。
// 版本号:不仅比值,还比戳
AtomicStampedReference<Integer> asr = new AtomicStampedReference<>(100, 0); // (初始值, 版本号)
Integer ref = asr.getReference(); // 获取引用
int stamp = asr.getStamp(); // 获取版本号
asr.compareAndSet(100, 101, stamp, stamp + 1);   // (旧值, 新值, 旧版本号, 新版本号)

自旋时间过长导致CPU占用率高

在高并发竞争激烈时,大量线程不断do-while重试,占用大量CPU资源。
解决方案:

  • 自适应自旋:重试一定次数后暂停,或使用Thread.yield()和指数退避。
  • 空间换时间:使用LongAdder。

只能保证单个共享变量的原子操作

CAS只针对单个内存位置,不支持多个变量。
解决方案:

  • 封装对象:将多个变量封装进一个类中,使用AtomicReference对整个对象进行CAS。
  • 使用锁:使用ReentrantLock或synchronized保护多变量的临界区。