欢迎光临
我们一直在努力

Java中的CAS机制详解

Java CAS(Compare And Swap)机制详解

一、概述

CAS(Compare And Swap,比较并交换) 是一种无锁(Lock-Free)的多线程同步机制,它基于硬件提供的原子性操作来实现线程安全。CAS 是现代并发编程中的核心技术,广泛应用于 Java 并发包中。

二、核心原理

1. 操作语义

CAS 操作包含三个参数:

  • V:要更新的内存地址(变量)
  • A:旧的预期值(Expected Value)
  • B:要设置的新值(New Value)

2. 执行流程

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 操作:

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 操作

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:实现线程安全的栈

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:实现自旋锁

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)

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 与锁的性能对比

    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. 使用现有原子类

    // ✅ 优先使用 JDK 提供的原子类
    AtomicInteger counter = new AtomicInteger(0);
    AtomicReference<User> userRef = new AtomicReference<>();

    2. 避免过度自旋

    // ❌ 纯自旋,CPU 消耗大
    while (!atomicRef.compareAndSet(oldValue, newValue)) {
    // 空循环
    }

    // ✅ 添加退避策略
    int retries = 0;
    while (!atomicRef.compareAndSet(oldValue, newValue)) {
    if (retries++ > 100) {
    Thread.yield();
    }
    // 或者使用指数退避
    }

    3. 解决 ABA 问题

    // 使用带版本号的原子引用
    AtomicStampedReference<Integer> ref =
    new AtomicStampedReference<>(0, 0);

    // 或者使用 AtomicMarkableReference
    AtomicMarkableReference<Integer> markedRef =
    new AtomicMarkableReference<>(0, false);

    4. 组合操作使用循环 CAS

    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,可以在保持线程安全的同时获得比传统锁机制更好的性能,特别是在多核处理器和高并发场景下。

    赞(0)
    未经允许不得转载:171主机测评 » Java中的CAS机制详解
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址