一句话说透Java里面的CAS原子操作

245 阅读2分钟

一、CAS是什么?

CAS(Compare And Swap)  可以理解为  “先检查再动手”  的机智操作。它的核心逻辑是:在修改一个值之前,先确认这个值是否和预期一致,如果一致才修改,否则放弃。这种操作是 原子性 的(不会被其他线程打断),所以不用加锁也能保证线程安全。


二、举个现实例子

假设你和朋友同时想修改共享文档里的一个数字:

  1. 你看到当前数字是 100,想改成 150

  2. CAS操作:文档系统会检查当前数字是否还是 100

    • 如果是 → 修改为 150,成功!
    • 如果不是 → 放弃修改,告诉你“有人改过了,请重试”。

这样即使多人同时操作,也不会出现数据错乱。


三、Java中的CAS实现

Java通过 Unsafe 调用CPU的原子指令(如x86的CMPXCHG),在以下场景广泛使用:

  • Atomic类(如 AtomicIntegerAtomicLong):

    AtomicInteger count = new AtomicInteger(0);
    count.incrementAndGet(); // 内部通过CAS实现自增
    
  • 并发容器(如 ConcurrentHashMap):CAS用于无锁化更新。

  • 锁机制(如 ReentrantLock):CAS实现锁的竞争。


四、CAS工作流程

以 AtomicInteger 的自增为例:

  1. 读取当前值(假设是 100)。

  2. 计算新值(100 + 1 = 101)。

  3. 执行CAS操作:

    • 检查内存中的值是否还是 100
    • 如果是 → 更新为 101,返回成功。
    • 如果被其他线程改成了 200 → 失败,重新读取并重试(自旋)。

五、CAS的优缺点

优点缺点
无锁,性能高(避免线程阻塞)ABA问题(后文详解)
代码简洁,避免死锁高并发下自旋可能浪费CPU资源

六、ABA问题与解决方案

1. ABA问题是什么?
  • 场景:线程A将值从 100 → 200 → 100,线程B的CAS操作看到值还是 100,误以为未被修改过。
  • 风险:数据看似没变,但中间过程可能已被篡改(比如银行余额先扣款又退回,但消费记录丢失)。
2. 解决方案
  • AtomicStampedReference:给值加版本号(类似“时间戳”)。

    AtomicStampedReference<Integer> ref = new AtomicStampedReference<>(100, 0);
    ref.compareAndSet(100, 150, 0, 1); // 检查值和版本号
    

七、适用场景

  1. 计数器(如网站访问量统计):

    AtomicLong visitCount = new AtomicLong(0);
    visitCount.incrementAndGet(); // CAS实现自增
    
  2. 无锁数据结构(如无锁队列、无锁栈)。

  3. 状态标记(如线程池的状态控制)。


八、总结

CAS 是并发编程的利器

  • 优势:轻量、高效,避免锁开销。
  • 注意:ABA问题需警惕,高竞争场景可能不如锁高效。

口诀
「CAS 操作真巧妙,先查后改保可靠
无锁并发性能高,ABA问题要记牢
Atomic类里显身手,版本号来解烦恼!」