深入浅出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应用场景或实现技巧,欢迎在评论区交流讨论~

相关推荐
争不过朝夕,又念着往昔28 分钟前
Go语言反射机制详解
开发语言·后端·golang
绝无仅有2 小时前
企微审批对接错误与解决方案
后端·算法·架构
Super Rookie2 小时前
Spring Boot 企业项目技术选型
java·spring boot·后端
来自宇宙的曹先生2 小时前
用 Spring Boot + Redis 实现哔哩哔哩弹幕系统(上篇博客改进版)
spring boot·redis·后端
expect7g3 小时前
Flink-Checkpoint-1.源码流程
后端·flink
00后程序员3 小时前
Fiddler中文版如何提升API调试效率:本地化优势与开发者实战体验汇总
后端
用户8122199367223 小时前
C# .Net Core零基础从入门到精通实战教程全集【190课】
后端
bobz9653 小时前
FROM scratch: docker 构建方式分析
后端
lzzy_lx_20893 小时前
Spring Boot登录认证实现学习心得:从皮肤信息系统项目中学到的经验
java·spring boot·后端
前端付豪4 小时前
21、用 Python + Pillow 实现「朋友圈海报图生成器」📸(图文合成 + 多模板 + 自动换行)
后端·python