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. 执行流程
booleanCAS(V,A,B){if(V==A){// 比较:当前值是否等于预期值V=B;// 交换:设置新值returntrue;// 成功}else{returnfalse;// 失败}}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 操作:
importsun.misc.Unsafe;importjava.lang.reflect.Field;publicclassCASExample{privatestaticUnsafe unsafe;privatevolatileint value;privatestaticlong 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){thrownewError(e);}}// 自定义 CAS 操作publicbooleancompareAndSet(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 操作
importjava.util.concurrent.atomic.AtomicInteger;publicclassBasicCASExample{privateAtomicInteger counter =newAtomicInteger(0);publicvoidincrement(){int oldValue, newValue;do{ oldValue = counter.get();// 读取当前值 newValue = oldValue +1;// 计算新值}while(!counter.compareAndSet(oldValue, newValue));// CAS循环直到成功}publicintgetValue(){return counter.get();}publicstaticvoidmain(String[] args)throwsInterruptedException{BasicCASExample example =newBasicCASExample();// 创建10个线程并发递增Thread[] threads =newThread[10];for(int i =0; i <10; i++){ threads[i]=newThread(()->{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:实现线程安全的栈
importjava.util.concurrent.atomic.AtomicReference;publicclassConcurrentStack<T>{privatestaticclassNode<T>{finalT value;Node<T> next;Node(T value){this.value = value;}}privateAtomicReference<Node<T>> top =newAtomicReference<>();// 无锁push操作publicvoidpush(T value){Node<T> newNode =newNode<>(value);Node<T> oldHead;do{ oldHead = top.get();// 读取当前栈顶 newNode.next = oldHead;// 新节点指向旧栈顶}while(!top.compareAndSet(oldHead, newNode));// CAS更新栈顶}// 无锁pop操作publicTpop(){Node<T> oldHead, newHead;do{ oldHead = top.get();// 读取当前栈顶if(oldHead ==null){returnnull;// 栈为空} newHead = oldHead.next;// 新栈顶为下一个节点}while(!top.compareAndSet(oldHead, newHead));// CAS更新栈顶return oldHead.value;}publicbooleanisEmpty(){return top.get()==null;}}示例 3:实现自旋锁
importjava.util.concurrent.atomic.AtomicBoolean;publicclassSpinLock{privateAtomicBoolean locked =newAtomicBoolean(false);publicvoidlock(){// 自旋等待直到获取锁while(!locked.compareAndSet(false,true)){// 可添加Thread.yield()或等待策略减少CPU占用Thread.yield();}}publicvoidunlock(){ locked.set(false);}publicbooleantryLock(){return locked.compareAndSet(false,true);}}示例 4:带版本号的 CAS(解决 ABA 问题)
什么是ABA问题见我的另一篇文章:ABA问题详解(Java)
importjava.util.concurrent.atomic.AtomicStampedReference;publicclassABASolutionExample{publicstaticvoidmain(String[] args){// 创建初始值,使用静态变量避免自动装箱创建新对象finalInteger value100 =100;finalInteger value200 =200;finalInteger value300 =300;// 初始值100,版本号0AtomicStampedReference<Integer> ref =newAtomicStampedReference<>(value100,0);int[] stampHolder =newint[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 -> Bint[] stampHolder1 =newint[1];Integer currentValue1 = ref.get(stampHolder1); ref.compareAndSet(currentValue1, value200, stampHolder1[0], stampHolder1[0]+1);// B -> Aint[] stampHolder2 =newint[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 与锁的性能对比
importjava.util.concurrent.CountDownLatch;importjava.util.concurrent.atomic.AtomicInteger;importjava.util.concurrent.locks.ReentrantLock;publicclassCASvsLockBenchmark{privatestaticfinalint THREAD_COUNT =10;privatestaticfinalint ITERATIONS =1000000;// 测试方法privatestaticlongbenchmark(Runnable task)throwsInterruptedException{CountDownLatch latch =newCountDownLatch(THREAD_COUNT);Thread[] threads =newThread[THREAD_COUNT];long start =System.currentTimeMillis();for(int i =0; i < THREAD_COUNT; i++){ threads[i]=newThread(()->{ task.run(); latch.countDown();}); threads[i].start();} latch.await();long end =System.currentTimeMillis();return end - start;}publicstaticvoidmain(String[] args)throwsInterruptedException{// 1. 测试 synchronizedObject lock =newObject();int[] syncCounter ={0};long syncTime =benchmark(()->{for(int j =0; j < ITERATIONS; j++){synchronized(lock){ syncCounter[0]++;}}});// 2. 测试 ReentrantLockReentrantLock reentrantLock =newReentrantLock();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 =newAtomicInteger(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 =newAtomicInteger(0);AtomicReference<User> userRef =newAtomicReference<>();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 =newAtomicStampedReference<>(0,0);// 或者使用 AtomicMarkableReferenceAtomicMarkableReference<Integer> markedRef =newAtomicMarkableReference<>(0,false);4. 组合操作使用循环 CAS
publicvoidaddIfLessThan(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,可以在保持线程安全的同时获得比传统锁机制更好的性能,特别是在多核处理器和高并发场景下。