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. 执行流程

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 的优缺点

优点

  1. 高性能:无锁操作,避免线程上下文切换
  2. 非阻塞:线程不会挂起,减少死锁风险
  3. 可扩展性好:在高并发场景下性能优势明显

缺点

  1. ABA 问题:值从 A 改为 B 再改回 A,CAS 无法察觉
  2. 自旋开销:竞争激烈时 CPU 空转浪费资源
  3. 只能保证一个变量的原子性:无法保证多个变量的复合操作
  4. 实现复杂:正确实现无锁算法难度较大

六、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 的场景

  1. 计数器AtomicIntegerAtomicLong
  2. 状态标志AtomicBoolean
  3. 无锁数据结构:栈、队列、集合
  4. 资源引用管理:对象池、连接池
  5. 版本控制:乐观锁实现

不适合使用 CAS 的场景

  1. 复杂业务逻辑:需要多个变量原子更新
  2. 竞争激烈场景:大量线程频繁 CAS 失败
  3. 需要阻塞等待:线程需要等待某些条件

八、最佳实践

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 是现代并发编程的基石,它通过硬件支持的原子操作实现了高效的无锁同步。在实际开发中:

  1. 优先选择 :使用 java.util.concurrent.atomic 包提供的原子类
  2. 注意限制:理解 CAS 的局限,特别是 ABA 问题和自旋开销
  3. 性能考量:在低到中度竞争场景使用 CAS,高度竞争时考虑其他方案
  4. 正确使用:复杂操作使用循环 CAS 模式,必要时添加退避策略

通过合理使用 CAS,可以在保持线程安全的同时获得比传统锁机制更好的性能,特别是在多核处理器和高并发场景下。

相关推荐
我星期八休息20 分钟前
IT疑难杂症诊疗室:AI时代工程师Superpowers进化论
linux·开发语言·数据结构·人工智能·python·散列表
热心网友俣先生29 分钟前
2026年第二十三届五一数学建模竞赛C题超详细解题思路+各问题可用模型推荐+部分模型结果展示
c语言·开发语言·数学建模
01漫游者34 分钟前
JavaScript函数与对象增强知识
开发语言·javascript·ecmascript
GottdesKrieges35 分钟前
OceanBase恢复常见问题
java·数据库·oceanbase
IGAn CTOU35 分钟前
Java高级开发进阶教程之系列
java·开发语言
leo825...39 分钟前
Claude Code Skills 清单(本地)
java·python·ai编程
csbysj202042 分钟前
SQL NULL 函数详解
开发语言
其实防守也摸鱼1 小时前
CTF密码学综合教学指南--第三章
开发语言·网络·python·安全·网络安全·密码学
NGSI vimp1 小时前
Java进阶——如何查看Java字节码
java·开发语言
We་ct2 小时前
深度剖析浏览器跨域问题
开发语言·前端·浏览器·跨域·cors·同源·浏览器跨域