深入浅出Java中的CAS:原理、源码与实战应用

一、什么是CAS?为什么我们需要它?

在多线程并发编程中,共享资源的原子性操作是一个经典难题。传统的synchronized锁虽然能保证线程安全,但会带来线程阻塞和唤醒的性能开销。这时CAS(Compare And Swap)作为一种无锁算法应运而生。

CAS的核心思想:我认为V的值应该是A,如果是的话我就把它改成B;如果不是A,说明有人改过,我就不修改了。这个比较和交换的过程是一个原子操作,由CPU指令直接保证。

二、深入CAS底层实现

2.1 Java中的Unsafe类

在HotSpot JVM中,CAS操作通过sun.misc.Unsafe类实现:

java 复制代码
public final class Unsafe {
    public final native boolean compareAndSwapInt(
        Object o, long offset, int expected, int x);
    
    // 其他CAS方法...
}

这个方法参数含义:

  • o:要操作的对象
  • offset:字段在对象中的偏移量
  • expected:预期原值
  • x:新值

2.2 AtomicInteger源码解析

以常用的AtomicInteger为例,看其如何实现原子递增:

java 复制代码
public class AtomicInteger extends Number {
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;
    
    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }
    
    private volatile int value;
    
    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }
}

Unsafe.getAndAddInt()的底层实现:

c++ 复制代码
UNSAFE_ENTRY(jint, Unsafe_GetAndAddInt(
    JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint addValue)) {
  oop p = JNIHandles::resolve(obj);
  jint* addr = (jint*)index_oop_from_field_offset_long(p, offset);
  return Atomic::add(addValue, addr);
} UNSAFE_END

可以看到最终调用的是CPU的原子指令(如x86的lock cmpxchg)。

三、开发实战中的CAS应用

3.1 高性能计数器

统计接口调用次数,使用AtomicLong:

java 复制代码
public class ApiCounter {
    private final AtomicLong counter = new AtomicLong(0);
    
    public void invoke() {
        // 业务逻辑...
        counter.incrementAndGet();
    }
    
    public long getCount() {
        return counter.get();
    }
}

3.2 实现自旋锁

通过CAS实现简单的自旋锁:

java 复制代码
public class SpinLock {
    private AtomicReference<Thread> owner = new AtomicReference<>();
    
    public void lock() {
        Thread current = Thread.currentThread();
        while (!owner.compareAndSet(null, current)) {
            // 自旋等待
        }
    }
    
    public void unlock() {
        Thread current = Thread.currentThread();
        owner.compareAndSet(current, null);
    }
}

3.3 实现无阻塞栈

使用CAS实现线程安全栈:

java 复制代码
public class ConcurrentStack<E> {
    private AtomicReference<Node<E>> top = new AtomicReference<>();
    
    public void push(E item) {
        Node<E> newHead = new Node<>(item);
        Node<E> oldHead;
        do {
            oldHead = top.get();
            newHead.next = oldHead;
        } while (!top.compareAndSet(oldHead, newHead));
    }
    
    public E pop() {
        Node<E> oldHead;
        Node<E> newHead;
        do {
            oldHead = top.get();
            if (oldHead == null) return null;
            newHead = oldHead.next;
        } while (!top.compareAndSet(oldHead, newHead));
        return oldHead.item;
    }
    
    private static class Node<E> {
        final E item;
        Node<E> next;
        
        Node(E item) { this.item = item; }
    }
}

四、CAS的ABA问题与解决方案

4.1 ABA问题演示

假设初始值为A:

  1. 线程1读取值为A
  2. 线程2修改A→B→A
  3. 线程1执行CAS:检测到值还是A,操作成功

虽然结果正确,但中间的状态变化可能引发问题。

4.2 使用AtomicStampedReference解决

通过版本号控制:

java 复制代码
public class AbaDemo {
    private static AtomicStampedReference<Integer> atomicRef = 
        new AtomicStampedReference<>(100, 0);
    
    public static void main(String[] args) {
        int stamp = atomicRef.getStamp();
        
        // 线程1尝试修改100→101
        new Thread(() -> {
            boolean success = atomicRef.compareAndSet(
                100, 101, stamp, stamp+1);
            System.out.println("Thread1 CAS: " + success);
        }).start();
        
        // 线程2执行A→B→A
        new Thread(() -> {
            atomicRef.compareAndSet(100, 101, stamp, stamp+1);
            atomicRef.compareAndSet(101, 100, stamp+1, stamp+2);
        }).start();
    }
}

五、CAS使用注意事项

  1. 自旋时间过长:CAS配合自旋使用时,需注意自旋次数限制
  2. 仅保证单个变量原子性:多个变量原子操作需使用其他手段
  3. 性能考量:在高争用场景下,CAS可能不如锁高效
  4. 平台依赖性:不同CPU架构实现可能不同

六、总结与思考

CAS作为无锁编程的基石,在Java并发包(java.util.concurrent)中被广泛应用。合理使用CAS可以:

  • 实现高性能的非阻塞算法
  • 减少线程上下文切换开销
  • 避免死锁风险

但也要根据具体场景选择方案:对于低竞争、简单操作推荐使用CAS;高竞争、复杂操作可能需要结合锁机制。


欢迎点赞收藏!如果有更好的CAS应用场景或实现技巧,欢迎在评论区交流讨论~

相关推荐
日月星辰Ace1 小时前
jwk-set-uri
java·后端
用户108386386801 小时前
95%开发者不知道的调试黑科技:Apipost让WebSocket开发效率翻倍的秘密
前端·后端
疏狂难除1 小时前
基于Rye的Django项目通过Pyinstaller用Github工作流简单打包
后端·python·django
钢板兽1 小时前
Java后端高频面经——JVM、Linux、Git、Docker
java·linux·jvm·git·后端·docker·面试
未完结小说2 小时前
声明式远程调用:OpenFeign 基础教程
后端
MickeyCV2 小时前
《苍穹外卖》SpringBoot后端开发项目重点知识整理(DAY1 to DAY3)
java·spring boot·后端·苍穹外卖
uhakadotcom2 小时前
ClickHouse入门:快速掌握高性能数据分析
后端·面试·github
雷渊2 小时前
深入分析mysql中的binlog和redo log
java·后端·面试
uhakadotcom2 小时前
Pydantic Extra Types:扩展数据类型的强大工具
后端·面试·github
uhakadotcom2 小时前
Spring Fu:让Spring Boot启动提速40%的黑科技
后端·面试·github