核心原理
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)
步骤1:CPU0读取0x1000
→ 缓存行状态变为E(独占)或S(如果其他CPU也有)
步骤2:执行LOCK CMPXCHG
→ CPU0发送"Read For Ownership"消息
→ 其他CPU的缓存行状态变为I(无效)
→ CPU0的缓存行状态变为M(已修改独占)
步骤3:CPU0完成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:
InterlockedCompareExchangeAPI - 提供统一接口,屏蔽硬件差异
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代码到硬件指令的完整映射。”