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

相关推荐
韩立学长3 小时前
【开题答辩实录分享】以《基于Python的大学超市仓储信息管理系统的设计与实现》为例进行选题答辩实录分享
开发语言·python
风筝在晴天搁浅3 小时前
hot100 78.子集
java·算法
froginwe113 小时前
Scala 循环
开发语言
m0_706653234 小时前
C++编译期数组操作
开发语言·c++·算法
故事和你914 小时前
sdut-Java面向对象-06 继承和多态、抽象类和接口(函数题:10-18题)
java·开发语言·算法·面向对象·基础语法·继承和多态·抽象类和接口
Bruk.Liu4 小时前
(LangChain实战2):LangChain消息(message)的使用
开发语言·langchain
qq_423233904 小时前
C++与Python混合编程实战
开发语言·c++·算法
m0_715575344 小时前
分布式任务调度系统
开发语言·c++·算法
Configure-Handler5 小时前
buildroot System configuration
java·服务器·数据库