【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 问题、自旋开销和单变量限制。

相关推荐
captain3761 小时前
Spring Web MVC(2)
java·前端·spring boot·后端·spring·mvc
紧跟先前的步伐1 小时前
Go AI Agent 实战 17|模型适配层设计:让模型可替换
开发语言·人工智能·后端·ai·golang
小灰灰搞电子1 小时前
Qt 中使用 QSettings 存储结构体数据详解
开发语言·qt
代码山河1 小时前
调试技巧:IDEA Debug模式入门与使用
java·学习·架构·教程·面向对象·项目
外收内放1 小时前
FastAPI猜字谜实战(5):会话管理
开发语言·python·学习·fastapi
Ai-_Man1 小时前
Microsoft Copilot对话数据怎么导入到另外一个Microsoft Copilot账号里去?AI导出鸭
开发语言·javascript·人工智能·microsoft·chatgpt·ecmascript·copilot
H.莓飛1 小时前
【数据结构】归并与外排序
linux·服务器·c语言·开发语言·数据结构·visual studio
文丑颜不良啊1 小时前
LeetCode851~875题目解析
java·leetcode
技术老孙1 小时前
给 Agent 装上对话框 —— AgentScope-Java 接入 Spring Boot 与 SSE 流式实战
java·开发语言·spring boot