Java CAS(Compare And Swap)机制详解
一、概述
CAS(Compare And Swap,比较并交换) 是一种无锁(Lock-Free)的多线程同步机制,它基于硬件提供的原子性操作来实现线程安全。CAS 是现代并发编程中的核心技术,广泛应用于 Java 并发包中。
二、核心原理
1. 操作语义
CAS 操作包含三个参数:
- V:要更新的内存地址(变量)
- A:旧的预期值(Expected Value)
- B:要设置的新值(New Value)
2. 执行流程
java
boolean CAS(V, A, B) {
if (V == A) { // 比较:当前值是否等于预期值
V = B; // 交换:设置新值
return true; // 成功
} else {
return false; // 失败
}
}
3. 硬件支持
CAS 操作在硬件层面是原子的,主要通过以下方式实现:
- x86/64 架构 :
CMPXCHG(Compare and Exchange)指令 - ARM 架构 :
LDREX/STREX(Load-Exclusive/Store-Exclusive)指令 - MIPS 架构 :
LL/SC(Load-Linked/Store-Conditional)指令
三、Java 中的 CAS 实现
1. sun.misc.Unsafe 类
Java 通过 Unsafe 类提供底层 CAS 操作:
java
import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class CASExample {
private static Unsafe unsafe;
private volatile int value;
private static long valueOffset;
static {
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (Unsafe) field.get(null);
valueOffset = unsafe.objectFieldOffset(
CASExample.class.getDeclaredField("value"));
} catch (Exception e) {
throw new Error(e);
}
}
// 自定义 CAS 操作
public boolean compareAndSet(int expectedValue, int newValue) {
return unsafe.compareAndSwapInt(this, valueOffset, expectedValue, newValue);
}
}
2. java.util.concurrent.atomic 包
Java 提供了丰富的原子类,内部均基于 CAS 实现:
| 类名 | 描述 | 常用方法 |
|---|---|---|
AtomicInteger |
原子整型 | get(), set(), compareAndSet(), incrementAndGet() |
AtomicLong |
原子长整型 | get(), addAndGet(), compareAndSet() |
AtomicBoolean |
原子布尔型 | get(), compareAndSet() |
AtomicReference<V> |
原子引用 | get(), set(), compareAndSet() |
AtomicIntegerArray |
原子整型数组 | getAndSet(), compareAndSet() |
AtomicStampedReference<V> |
带版本号的原子引用 | 解决 ABA 问题 |
四、完整使用示例
示例 1:基础 CAS 操作
java
import java.util.concurrent.atomic.AtomicInteger;
public class BasicCASExample {
private AtomicInteger counter = new AtomicInteger(0);
public void increment() {
int oldValue, newValue;
do {
oldValue = counter.get(); // 读取当前值
newValue = oldValue + 1; // 计算新值
} while (!counter.compareAndSet(oldValue, newValue)); // CAS循环直到成功
}
public int getValue() {
return counter.get();
}
public static void main(String[] args) throws InterruptedException {
BasicCASExample example = new BasicCASExample();
// 创建10个线程并发递增
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
example.increment();
}
});
threads[i].start();
}
for (Thread t : threads) {
t.join();
}
System.out.println("最终结果: " + example.getValue()); // 输出: 10000
}
}
示例 2:实现线程安全的栈
java
import java.util.concurrent.atomic.AtomicReference;
public class ConcurrentStack<T> {
private static class Node<T> {
final T value;
Node<T> next;
Node(T value) {
this.value = value;
}
}
private AtomicReference<Node<T>> top = new AtomicReference<>();
// 无锁push操作
public void push(T value) {
Node<T> newNode = new Node<>(value);
Node<T> oldHead;
do {
oldHead = top.get(); // 读取当前栈顶
newNode.next = oldHead; // 新节点指向旧栈顶
} while (!top.compareAndSet(oldHead, newNode)); // CAS更新栈顶
}
// 无锁pop操作
public T pop() {
Node<T> oldHead, newHead;
do {
oldHead = top.get(); // 读取当前栈顶
if (oldHead == null) {
return null; // 栈为空
}
newHead = oldHead.next; // 新栈顶为下一个节点
} while (!top.compareAndSet(oldHead, newHead)); // CAS更新栈顶
return oldHead.value;
}
public boolean isEmpty() {
return top.get() == null;
}
}
示例 3:实现自旋锁
java
import java.util.concurrent.atomic.AtomicBoolean;
public class SpinLock {
private AtomicBoolean locked = new AtomicBoolean(false);
public void lock() {
// 自旋等待直到获取锁
while (!locked.compareAndSet(false, true)) {
// 可添加Thread.yield()或等待策略减少CPU占用
Thread.yield();
}
}
public void unlock() {
locked.set(false);
}
public boolean tryLock() {
return locked.compareAndSet(false, true);
}
}
示例 4:带版本号的 CAS(解决 ABA 问题)
什么是ABA问题见我的另一篇文章:ABA问题详解(Java)
java
import java.util.concurrent.atomic.AtomicStampedReference;
public class ABASolutionExample {
public static void main(String[] args) {
// 创建初始值,使用静态变量避免自动装箱创建新对象
final Integer value100 = 100;
final Integer value200 = 200;
final Integer value300 = 300;
// 初始值100,版本号0
AtomicStampedReference<Integer> ref =
new AtomicStampedReference<>(value100, 0);
int[] stampHolder = new int[1];
int oldValue = ref.get(stampHolder);
int oldStamp = stampHolder[0];
System.out.println("初始: 值=" + oldValue + ", 版本=" + oldStamp);
// 模拟ABA
// ====================================================================
// 注意:不能写成这样,否则Integer对象会被缓存,导致版本号不变
// 步骤1:100 → 200 ref.compareAndSet(100, 200, 0, 1); 这里的100和200都是自动装箱的Integer对象
// 步骤2:200 → 100 ref.compareAndSet(200, 100, 1, 2); 这里的200是新创建的Integer对象,与步骤1中的200不是同一个对象
// ====================================================================
// A -> B
int[] stampHolder1 = new int[1];
Integer currentValue1 = ref.get(stampHolder1);
ref.compareAndSet(currentValue1, value200, stampHolder1[0], stampHolder1[0] + 1);
// B -> A
int[] stampHolder2 = new int[1];
Integer currentValue2 = ref.get(stampHolder2);
ref.compareAndSet(currentValue2, value100, stampHolder2[0], stampHolder2[0] + 1);
// 尝试CAS(会失败,因为版本变了)
boolean success = ref.compareAndSet(oldValue, value300, oldStamp, oldStamp + 1);
System.out.println("结果: " + (success ? "成功" : "失败"));
System.out.println("原因: 版本从" + oldStamp + "变为" + ref.getStamp());
}
}
五、CAS 的优缺点
✅ 优点
- 高性能:无锁操作,避免线程上下文切换
- 非阻塞:线程不会挂起,减少死锁风险
- 可扩展性好:在高并发场景下性能优势明显
❌ 缺点
- ABA 问题:值从 A 改为 B 再改回 A,CAS 无法察觉
- 自旋开销:竞争激烈时 CPU 空转浪费资源
- 只能保证一个变量的原子性:无法保证多个变量的复合操作
- 实现复杂:正确实现无锁算法难度较大
六、CAS 与锁的性能对比
java
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
public class CASvsLockBenchmark {
private static final int THREAD_COUNT = 10;
private static final int ITERATIONS = 1000000;
// 测试方法
private static long benchmark(Runnable task) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(THREAD_COUNT);
Thread[] threads = new Thread[THREAD_COUNT];
long start = System.currentTimeMillis();
for (int i = 0; i < THREAD_COUNT; i++) {
threads[i] = new Thread(() -> {
task.run();
latch.countDown();
});
threads[i].start();
}
latch.await();
long end = System.currentTimeMillis();
return end - start;
}
public static void main(String[] args) throws InterruptedException {
// 1. 测试 synchronized
Object lock = new Object();
int[] syncCounter = {0};
long syncTime = benchmark(() -> {
for (int j = 0; j < ITERATIONS; j++) {
synchronized (lock) {
syncCounter[0]++;
}
}
});
// 2. 测试 ReentrantLock
ReentrantLock reentrantLock = new ReentrantLock();
int[] lockCounter = {0};
long lockTime = benchmark(() -> {
for (int j = 0; j < ITERATIONS; j++) {
reentrantLock.lock();
try {
lockCounter[0]++;
} finally {
reentrantLock.unlock();
}
}
});
// 3. 测试 CAS (AtomicInteger)
AtomicInteger atomicCounter = new AtomicInteger(0);
long atomicTime = benchmark(() -> {
for (int j = 0; j < ITERATIONS; j++) {
atomicCounter.incrementAndGet();
}
});
System.out.println("测试结果(越低越好):");
System.out.println("synchronized: " + syncTime + "ms");
System.out.println("ReentrantLock: " + lockTime + "ms");
System.out.println("CAS (AtomicInteger): " + atomicTime + "ms");
// 验证结果
System.out.println("\n最终计数验证:");
System.out.println("synchronized: " + syncCounter[0]);
System.out.println("ReentrantLock: " + lockCounter[0]);
System.out.println("CAS: " + atomicCounter.get());
}
}
典型测试结果(10个线程,每个递增100万次):
- synchronized: ~1200ms
- ReentrantLock: ~800ms
- CAS (AtomicInteger): ~400ms
七、CAS 的适用场景
✅ 适合使用 CAS 的场景
- 计数器 :
AtomicInteger、AtomicLong - 状态标志 :
AtomicBoolean - 无锁数据结构:栈、队列、集合
- 资源引用管理:对象池、连接池
- 版本控制:乐观锁实现
❌ 不适合使用 CAS 的场景
- 复杂业务逻辑:需要多个变量原子更新
- 竞争激烈场景:大量线程频繁 CAS 失败
- 需要阻塞等待:线程需要等待某些条件
八、最佳实践
1. 使用现有原子类
java
// ✅ 优先使用 JDK 提供的原子类
AtomicInteger counter = new AtomicInteger(0);
AtomicReference<User> userRef = new AtomicReference<>();
2. 避免过度自旋
java
// ❌ 纯自旋,CPU 消耗大
while (!atomicRef.compareAndSet(oldValue, newValue)) {
// 空循环
}
// ✅ 添加退避策略
int retries = 0;
while (!atomicRef.compareAndSet(oldValue, newValue)) {
if (retries++ > 100) {
Thread.yield();
}
// 或者使用指数退避
}
3. 解决 ABA 问题
java
// 使用带版本号的原子引用
AtomicStampedReference<Integer> ref =
new AtomicStampedReference<>(0, 0);
// 或者使用 AtomicMarkableReference
AtomicMarkableReference<Integer> markedRef =
new AtomicMarkableReference<>(0, false);
4. 组合操作使用循环 CAS
java
public void addIfLessThan(int max) {
int current;
int newValue;
do {
current = atomicInt.get();
if (current >= max) {
return; // 条件不满足
}
newValue = current + 1;
} while (!atomicInt.compareAndSet(current, newValue));
}
九、总结
CAS 是现代并发编程的基石,它通过硬件支持的原子操作实现了高效的无锁同步。在实际开发中:
- 优先选择 :使用
java.util.concurrent.atomic包提供的原子类 - 注意限制:理解 CAS 的局限,特别是 ABA 问题和自旋开销
- 性能考量:在低到中度竞争场景使用 CAS,高度竞争时考虑其他方案
- 正确使用:复杂操作使用循环 CAS 模式,必要时添加退避策略
通过合理使用 CAS,可以在保持线程安全的同时获得比传统锁机制更好的性能,特别是在多核处理器和高并发场景下。