【JUC】CAS

Java多线程中的CAS(Compare-And-Swap)

CAS 是 Java 并发编程的核心基石之一,全称 Compare-And-Swap(比较并交换),是一种**无锁(lock-free)**的原子操作。


一、CAS 的核心思想

CAS 包含三个操作数:

参数 含义
V (Value) 内存中的当前值
A (Expected) 期望的旧值
B (New) 要更新的新值

执行逻辑:

当且仅当内存值 V == 期望值 A 时,才把内存值更新为 B;否则不做任何操作。整个"比较+更新"是一个原子操作 ,由 CPU 指令保证(如 x86 的 cmpxchg)。

复制代码
if (V == A) {
    V = B;
    return true;
} else {
    return false;
}

二、Java 中的 CAS 实现

1. Unsafe 类(底层)

Java 的 CAS 最终都依赖于 sun.misc.Unsafe 提供的 native 方法:

java 复制代码
public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);
public final native boolean compareAndSwapLong(Object o, long offset, long expected, long x);
public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object x);

这些方法通过 JNI 调用底层 CPU 的 CAS 指令。

2. 原子类(AtomicXXX)

java.util.concurrent.atomic 包下的类是最典型的 CAS 应用:

java 复制代码
public class AtomicInteger extends Number {
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;  // value 字段的内存偏移量
    private volatile int value;

    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

    // 典型用法:自增
    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }
}

注意 :value 用 volatile 修饰,保证可见性;valueOffset 通过反射获取字段内存地址。

3. 自旋 + CAS 模式

以 getAndAddInt 为例,展示经典的自旋 CAS 写法:

java 复制代码
public final int getAndAddInt(Object o, long offset, int delta) {
    int v;
    do {
        v = getIntVolatile(o, offset);  // 读取最新值
    } while (!compareAndSwapInt(o, offset, v, v + delta));  // CAS 失败则重试
    return v;
}

三、CAS 的三大问题

1. ABA 问题 ⭐

问题:线程1读到值为 A,准备 CAS 改为 C;期间线程2把 A→B→A。线程1的 CAS 仍然成功,但值已经被"偷偷"改过。

解决 :使用 版本号 / 时间戳 ,即 AtomicStampedReference:

java 复制代码
AtomicStampedReference<Integer> ref = new AtomicStampedReference<>(100, 0);

int stamp = ref.getStamp();
ref.compareAndSet(100, 200, stamp, stamp + 1);  // 同时校验值和版本号

类似的还有 AtomicMarkableReference(用一个 boolean 标记)。

2. 自旋开销大 ⭐

CAS 失败会一直循环重试。在高并发竞争激烈时,大量线程空转消耗 CPU。

优化:

  • LongAdder / DoubleAdder(分段累加,减少竞争)
  • 适当退让(Thread.yield())或加锁策略
  • JDK 内部会调用 pause 指令降低流水线开销
3. 只能保证单个变量的原子性

CAS 只对一个内存位置有效。若需多个变量原子更新:

解决 :使用 AtomicReference 封装一个对象:

java 复制代码
class Point { int x, y; }
AtomicReference<Point> pointRef = new AtomicReference<>(new Point());

// 用 CAS 整体替换引用(每次创建新对象,避免可变状态)
pointRef.compareAndSet(oldPoint, new Point(1, 2));

四、CAS 的优缺点

优点 缺点
无锁,无阻塞,性能高 ABA 问题
避免线程上下文切换 高竞争时自旋消耗 CPU
由硬件指令保证原子性 只能保证单变量原子性
适合读多写少场景 代码复杂,不易理解调试

五、CAS 的典型应用场景

  1. 原子类 :AtomicInteger、AtomicLong、AtomicReference
  2. AQS(AbstractQueuedSynchronizer) :ReentrantLock、Semaphore 等的 state 字段更新
  3. 并发容器 :ConcurrentHashMap 的节点插入(部分用 CAS)
  4. LongAdder 的分段计数
  5. 无锁数据结构:无锁队列、无锁栈

六、总结(一句话)

CAS 是通过硬件原子指令实现"比较并交换"的无锁并发原语,是 Java 原子类和 AQS 的底层基石;使用时需注意 ABA 问题、自旋开销和单变量限制。

相关推荐
乌暮42 分钟前
Spring AOP 与 Spring 事务:用大白话彻底理解原理与实践
java·数据库·spring boot·后端·sql·学习·spring
YDS8291 小时前
Small Spring IOC篇:容器事件及其监听器
java·spring
金銀銅鐵1 小时前
带您看懂字节码指令(三):一行加法里有什么
java·jvm
文丑颜不良啊1 小时前
LeetCode2921~2940题目解析
java·leetcode
北漂人Java2 小时前
基于 Spring AI Alibaba WorkFlow 构建 Java 生产级 AI Agent
java·人工智能·spring
用户112936112193 小时前
编译全绿,上线就崩:MyBatis 五层方法名大小写埋下的坑
java
文丑颜不良啊3 小时前
LeetCode2901~2920题目解析
java·leetcode
码云社区3 小时前
能碳管理系统开发要关注什么?能源数据、统计口径与AI分析的衔接
java·人工智能·ai·能源·能源管理系统·能碳管理系统·能碳管理系统源码
Flynn Liang4 小时前
Agentic 工作流:子代理、Supervisor 与人工介入
java·langchain·多智能体·supervisor·agentic·langchain4j
xys_6784 小时前
JavaSE通关手册:运算符
java·java-ee