核心原理

CAS的原子性由CPU硬件指令保证,操作系统和JVM只是提供了访问这些硬件指令的接口。

硬件层面实现

1. CPU指令

不同架构的CPU提供了专门的原子指令:

CPU架构 原子指令 说明
x86/x64 CMPXCHG + LOCK 前缀 比较并交换指令
ARM LDREX / STREX 加载独占 / 存储独占
MIPS LL / SC 加载链接 / 条件存储
PowerPC LWARX / STWCX 加载保留 / 条件存储

2. x86架构详解(最常见)

指令格式

; CMPXCHG指令格式
LOCK CMPXCHG [内存地址], 寄存器

; 伪代码逻辑
if (EAX == [内存地址]) {
    ZF = 1;  // 设置零标志位
    [内存地址] = 寄存器;  // 交换
} else {
    ZF = 0;
    EAX = [内存地址];  // 读取当前值
}

LOCK前缀的作用

LOCK前缀确保指令执行期间的原子性,通过以下机制:

早期CPU(Pentium及之前)

  • 锁总线(Bus Lock):锁定内存总线,阻止其他CPU访问内存
  • 缺点:性能开销极大,阻塞所有CPU

现代CPU(Pentium 4之后)

  • 锁缓存行(Cache Line Lock):只锁定特定缓存行
  • 利用MESI协议:通过缓存一致性协议保证原子性
  • 性能提升:仅影响同一缓存行的操作

3. 缓存一致性协议(MESI)

MESI协议状态

M (Modified)   - 已修改:当前CPU独占,且数据已修改
E (Exclusive)  - 独占:当前CPU独占,数据未修改
S (Shared)     - 共享:多个CPU共享,数据一致
I (Invalid)    - 无效:缓存行无效

CAS操作的MESI流程

// 假设:int value = 0; 在地址0x1000
// CPU0执行:compareAndSwap(0x1000, 0, 1)

步骤1CPU0读取0x1000
   缓存行状态变为E独占或S如果其他CPU也有

步骤2执行LOCK CMPXCHG
   CPU0发送"Read For Ownership"消息
   其他CPU的缓存行状态变为I无效
   CPU0的缓存行状态变为M已修改独占

步骤3CPU0完成CAS
   其他CPU在此期间无法访问该缓存行
   保证了原子性

4. ARM架构的LL/SC机制

; ARM的CAS实现(伪代码)
retry:
    LDREX R0, [R1]       ; 加载独占:R0 = *R1,并标记监视
    CMP R0, R2           ; 比较:R0 == R2 ?
    BNE fail             ; 不相等则失败
    STREX R3, R4, [R1]   ; 存储独占:*R1 = R4,R3=成功标志
    CMP R3, #0           ; 检查是否成功
    BNE retry            ; 失败则重试
    B success
fail:
    ; 失败处理
success:
    ; 成功处理

原理

  • LDREX:标记内存地址为”独占监视”
  • STREX:检查监视状态,若有其他CPU访问则失败
  • 硬件监视:由CPU的”独占监视器”跟踪

操作系统层面

1. 系统调用路径

Java代码
  ↓
Unsafe.compareAndSwapInt() [native方法]
  ↓
JVM (Hotspot)
  ↓
JNI调用
  ↓
操作系统内核(提供原子操作API)
  ↓
CPU指令(CMPXCHG + LOCK)

2. Linux内核中的实现

// Linux内核的atomic_cmpxchg实现
// 位于:arch/x86/include/asm/cmpxchg.h

#define cmpxchg(ptr, old, new) \
    __cmpxchg(ptr, old, new, sizeof(*(ptr)))

static inline unsigned long __cmpxchg(volatile void *ptr, 
                                      unsigned long old,
                                      unsigned long new, 
                                      int size)
{
    unsigned long prev;
    switch (size) {
    case 4:
        asm volatile(
            LOCK_PREFIX "cmpxchgl %k1,%2"  // LOCK前缀 + CMPXCHG指令
            : "=a"(prev)                    // 输出:prev = EAX
            : "r"(new), "m"(*ptr), "0"(old) // 输入
            : "memory"                      // 内存屏障
        );
        return prev;
    // ... 其他大小
    }
}

关键点

  • LOCK_PREFIX:根据CPU是否支持,决定是否添加LOCK前缀
  • memory屏障:防止编译器重排序

3. Windows内核中的实现

// Windows API:InterlockedCompareExchange
LONG InterlockedCompareExchange(
    LONG volatile *Destination,
    LONG Exchange,
    LONG Comparand
);

// 底层也是CMPXCHG指令
__asm {
    mov eax, Comparand
    mov edx, Exchange
    mov ecx, Destination
    lock cmpxchg [ecx], edx  // 使用LOCK前缀
}

JVM层面实现

1. Hotspot JVM源码

// hotspot/src/share/vm/runtime/atomic.cpp
jint Atomic::cmpxchg(jint exchange_value, 
                     volatile jint* dest, 
                     jint compare_value) {
  // x86平台
  #ifdef AMD64
    __asm__ volatile (
      "lock cmpxchgl %1,(%3)"
      : "=a" (exchange_value)
      : "r" (exchange_value), "a" (compare_value), "r" (dest)
      : "cc", "memory"
    );
    return exchange_value;
  #endif
  
  // ARM平台
  #ifdef ARM
    return __sync_val_compare_and_swap(dest, compare_value, exchange_value);
  #endif
}

2. Java到汇编的映射

// Java代码
AtomicInteger ai = new AtomicInteger(0);
ai.compareAndSet(0, 1);

// ↓ 编译后的字节码
invokevirtual #2  // Method java/util/concurrent/atomic/AtomicInteger.compareAndSet

// ↓ JIT编译后的汇编(x86-64)
0x00007f8b2c001234: mov    0x10(%rsi),%eax    ; 读取value字段
0x00007f8b2c001237: cmp    %eax,%ecx          ; 比较期望值
0x00007f8b2c001239: jne    0x00007f8b2c001250 ; 不相等则跳转
0x00007f8b2c00123f: lock cmpxchg %edx,0x10(%rsi) ; CAS指令
0x00007f8b2c001244: sete   %al                ; 设置返回值

多核场景下的原子性保证

1. 单核CPU

单核CPU:
  - 不需要LOCK前缀
  - 指令本身不可中断(微码级原子性)
  - 但仍需防止编译器重排序

2. 多核CPU

多核CPU:
  - 必须使用LOCK前缀
  - 通过以下机制保证原子性:
    1. 缓存行锁定(现代CPU)
    2. 总线锁定(旧CPU)
    3. MESI协议(缓存一致性)

3. 实例演示

public class MultiCoreAtomicityDemo {
    private static AtomicInteger counter = new AtomicInteger(0);
    
    public static void main(String[] args) throws InterruptedException {
        // 8个线程并发执行(对应多核CPU)
        Thread[] threads = new Thread[8];
        for (int i = 0; i < 8; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 100000; j++) {
                    counter.incrementAndGet();  // CAS操作
                }
            });
            threads[i].start();
        }
        
        for (Thread t : threads) {
            t.join();
        }
        
        System.out.println("最终值:" + counter.get());
        System.out.println("预期值:" + (8 * 100000));
        // 输出:最终值:800000 (永远正确)
    }
}

原子性保证

  • 每个CPU执行CAS时,通过LOCK前缀锁定缓存行
  • 其他CPU的缓存行被标记为Invalid
  • 保证同一时刻只有一个CPU能成功修改

性能考量

1. LOCK前缀的开销

public class LockOverheadTest {
    static AtomicInteger atomic = new AtomicInteger(0);
    static volatile int volatileVar = 0;
    static int normalVar = 0;
    
    // 测试CAS(带LOCK前缀)
    @Benchmark
    public void testCAS() {
        atomic.incrementAndGet();  // ~20ns
    }
    
    // 测试volatile写(带LOCK前缀)
    @Benchmark
    public void testVolatile() {
        volatileVar++;  // ~10ns(写) + ~30ns(读改写)
    }
    
    // 测试普通变量(无LOCK)
    @Benchmark
    public void testNormal() {
        normalVar++;  // ~1ns(但非线程安全)
    }
}

结论

  • 普通操作:1ns
  • volatile写:10ns
  • CAS操作:20-50ns(包含重试)

2. 缓存行伪共享问题

// 问题:两个AtomicLong在同一缓存行
class BadPadding {
    AtomicLong value1 = new AtomicLong();
    AtomicLong value2 = new AtomicLong();  // 可能在同一缓存行
}

// 解决:填充避免伪共享
class GoodPadding {
    AtomicLong value1 = new AtomicLong();
    long p1, p2, p3, p4, p5, p6, p7;  // 填充56字节
    AtomicLong value2 = new AtomicLong();  // 确保不在同一缓存行
}

// 或使用@Contended注解(JDK 8+)
@sun.misc.Contended
class ContendedPadding {
    AtomicLong value1 = new AtomicLong();
    @sun.misc.Contended
    AtomicLong value2 = new AtomicLong();
}

答题总结

分层解释

硬件层

  • x86架构:LOCK CMPXCHG指令,LOCK前缀锁定缓存行
  • 现代CPU:通过MESI缓存一致性协议保证原子性
  • ARM架构:LDREX/STREX独占监视机制

操作系统层

  • Linux:cmpxchg内核函数,封装汇编指令
  • Windows:InterlockedCompareExchange API
  • 提供统一接口,屏蔽硬件差异

JVM层

  • Unsafe类的native方法调用操作系统API
  • JIT编译器将字节码翻译为机器码
  • 直接生成带LOCK前缀的CMPXCHG指令

面试答题模板

“CAS的原子性是由CPU硬件指令保证的。

在x86架构上,JVM会生成LOCK CMPXCHG指令,LOCK前缀在现代CPU上通过锁定缓存行来保证原子性。具体来说,CPU会利用MESI缓存一致性协议,当一个CPU执行CAS时,会将相关缓存行标记为独占(Exclusive)或已修改(Modified),同时让其他CPU的对应缓存行失效(Invalid),这样就保证了同一时刻只有一个CPU能成功修改内存值。

在操作系统层面,Linux内核提供了atomic_cmpxchg函数,Windows提供了InterlockedCompareExchange API,它们都是对底层CPU指令的封装。

JVM通过Unsafe类的native方法调用这些操作系统API,并在JIT编译时直接生成对应的机器码,实现了从Java代码到硬件指令的完整映射。”