CAS 深度解析:从硬件指令到源码实现的全面剖析

一、引言:并发编程的"原子性"难题

在多线程编程中,原子性 是最基本也最棘手的问题之一。经典的 i++ 操作看似一行代码,在底层却被拆分为"读取-修改-写入"三个步骤。多个线程同时执行时,会出现数据不一致的问题。

传统的解决方案是加锁synchronizedReentrantLock),但锁会带来线程上下文切换、阻塞唤醒的开销。在低竞争场景下,加锁的性能损失远大于操作本身。

有没有一种机制,既保证原子性,又避免锁的开销?

CAS(Compare-And-Swap,比较并交换) 给出了答案。它是一种乐观锁技术,允许线程在不加锁的情况下安全地更新共享变量,是现代并发编程的基石。

CAS 的核心思想 :先读取 变量的当前值,然后比较 当前值是否与预期值一致,如果一致则交换 为新值,否则失败重试。整个过程是硬件级别的 原子操作

二、什么是 CAS?

2.1 定义与操作原语

CAS 是一个原子操作,包含三个操作数:

  • V:要操作的内存位置(变量)

  • E:期望的旧值(Expected value)

  • N:要写入的新值(New value)

执行逻辑:读取 V 的当前值,与 E 比较,如果相等则将 V 更新为 N,否则什么都不做。无论成功与否,都返回 V 的旧值。

java 复制代码
// CAS 的伪代码表示(实际由硬件原子完成)
boolean compareAndSwap(V, E, N) {
    if (V == E) {
        V = N;
        return true;
    }
    return false;
}

2.2 为什么 CAS 是"无锁"的?

CAS 操作由CPU 硬件指令 直接支持,在单条指令周期内完成,不会被线程调度中断,因此天然线程安全 。使用 CAS 实现的同步机制,线程不会阻塞,也就没有上下文切换的开销,被称为非阻塞同步

三、Java 中的 CAS 实现:Unsafe 类

在 Java 中,CAS 操作是通过 sun.misc.Unsafe 类提供的本地方法实现的。Unsafe 是 Java 中用于执行底层、不安全的操作的类,它绕过了 Java 的访问控制,直接操作内存。

3.1 Unsafe 的关键方法

java 复制代码
// Unsafe 类中的 CAS 方法(简化)
public final native boolean compareAndSwapObject(Object obj, long offset, Object expect, Object update);
public final native boolean compareAndSwapInt(Object obj, long offset, int expect, int update);
public final native boolean compareAndSwapLong(Object obj, long offset, long expect, long update);
  • obj:要操作的对象

  • offset:该对象中字段的内存偏移量(通过 Unsafe.objectFieldOffset() 获得)

  • expect:期望值

  • update:新值

3.2 获取 Unsafe 实例

Unsafe 是单例模式,构造方法私有,只能通过 Unsafe.getUnsafe() 获取,但该方法会检查调用类是否由 Bootstrap ClassLoader 加载,普通应用无法直接调用。

java 复制代码
// 通过反射获取 Unsafe 实例
public static Unsafe getUnsafe() {
    try {
        Field field = Unsafe.class.getDeclaredField("theUnsafe");
        field.setAccessible(true);
        return (Unsafe) field.get(null);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

注意 :官方不建议开发者直接使用 Unsafe,因为它的操作不安全,可能导致 JVM 崩溃。但它为 java.util.concurrent 包提供了底层支撑,我们学习源码即可,不鼓励在业务代码中使用。

3.3 使用 Unsafe 实现自定义原子计数器

java 复制代码
import sun.misc.Unsafe;
import java.lang.reflect.Field;

public class AtomicCounter {
    private volatile long value;
    private static final Unsafe UNSAFE;
    private static final long VALUE_OFFSET;
    
    static {
        try {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            UNSAFE = (Unsafe) field.get(null);
            VALUE_OFFSET = UNSAFE.objectFieldOffset(AtomicCounter.class.getDeclaredField("value"));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    public AtomicCounter(long initialValue) {
        this.value = initialValue;
    }
    
    /**
     * 原子性加 1,返回旧值
     */
    public long getAndIncrement() {
        return UNSAFE.getAndAddLong(this, VALUE_OFFSET, 1L);
    }
    
    /**
     * 自定义 CAS 更新
     */
    public boolean compareAndSet(long expect, long update) {
        return UNSAFE.compareAndSwapLong(this, VALUE_OFFSET, expect, update);
    }
    
    public long get() {
        return value;
    }
}

四、源码阅读:AtomicLong 的 CAS 实现

AtomicLong 是 Java 原子包中最常用的类之一,它的底层完全依赖 Unsafe 进行 CAS 操作。

4.1 成员变量与初始化

java 复制代码
public class AtomicLong extends Number implements java.io.Serializable {
    private static final Unsafe U = Unsafe.getUnsafe();
    private static final long VALUE;
    
    // 实际存储的值,volatile 保证可见性
    private volatile long value;
    
    static {
        try {
            // 获取 value 字段的内存偏移量
            VALUE = U.objectFieldOffset(AtomicLong.class.getDeclaredField("value"));
        } catch (ReflectiveOperationException e) {
            throw new Error(e);
        }
    }
}

4.2 getAndIncrement() 源码

java 复制代码
public final long getAndIncrement() {
    // 委托给 Unsafe 的 getAndAddLong
    return U.getAndAddLong(this, VALUE, 1L);
}

4.3 Unsafe.getAndAddLong() 源码

java 复制代码
// sun.misc.Unsafe
public final long getAndAddLong(Object obj, long offset, long delta) {
    long v;
    do {
        // 循环读取当前值
        v = getLongVolatile(obj, offset);
        // CAS 尝试更新,失败则重试
    } while (!compareAndSwapLong(obj, offset, v, v + delta));
    return v;
}

核心逻辑

  1. getLongVolatile :读取变量当前值,带 volatile 语义(从主内存读取)

  2. compareAndSwapLong:通过 JNI 调用 CPU 指令执行 CAS

  3. 自旋:如果 CAS 失败,循环重试直到成功

4.4 JNI 层面的实现(HotSpot 源码)

Unsafe.compareAndSwapLong 最终调用的是 OpenJDK 中的 JVM 函数:

java 复制代码
// openjdk/hotspot/src/share/vm/prims/unsafe.cpp
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapLong(JNIEnv *env, jobject unsafe, 
                                                   jobject obj, jlong offset,
                                                   jlong e, jlong x)) {
  oop p = JNIHandles::resolve(obj);
  volatile jlong* addr = (volatile jlong*)index_oop_from_field_offset_long(p, offset);
  // 调用 Atomic::cmpxchg 模板函数,最终映射到 CPU 的 CMPXCHG 指令
  return Atomic::cmpxchg(addr, e, x) == e;
}
UNSAFE_END

对于 x86 架构,Atomic::cmpxchg 最终会调用 LOCK CMPXCHG 指令(或 LOCK CMPXCHGQ 对于 long 类型),在多核 CPU 中通过 总线锁定缓存锁定 保证原子性。

五、CAS 的三大问题与解决方案

5.1 ABA 问题

问题描述:线程 T1 读取值 A,然后被挂起;线程 T2 将 A 改为 B 又改回 A;T1 恢复后 CAS 发现值还是 A,误以为没有被修改过,执行更新。

解决方案

  • AtomicStampedReference:携带版本号(stamp),每次修改版本号+1

  • AtomicMarkableReference:携带布尔标记位

java 复制代码
import java.util.concurrent.atomic.AtomicStampedReference;

public class ABADemo {
    public static void main(String[] args) {
        AtomicStampedReference<Integer> ref = new AtomicStampedReference<>(100, 0);
        
        // 线程1:期望值为100,版本号为0,尝试改为101(版本号+1)
        int stamp = ref.getStamp();
        boolean success = ref.compareAndSet(100, 101, stamp, stamp + 1);
        System.out.println("CAS成功?" + success + " 新值:" + ref.getReference());
        // 即使别的线程将值改回100,版本号已变,CAS会失败
    }
}

5.2 自旋开销问题

问题描述:在高并发下,CAS 失败率高,大量线程自旋重试,消耗 CPU。

解决方案

  • 自适应自旋(JVM 内部优化)

  • 使用 LongAdder 替代 AtomicLong(将竞争分散到多个 Cell)

  • 失败时让出 CPU(Thread.yield())或短暂休眠

5.3 单变量原子性限制

问题描述 :CAS 只能原子操作一个共享变量。

解决方案

  • 使用 AtomicReference 包装多个变量

  • 或者使用 Lock 机制(锁可以原子操作多个变量)

六、性能分析

6.1 CAS vs 锁的性能对比

|-----|----------------|-----------------|
| 场景 | CAS(自旋) | 锁(synchronized) |
| 低竞争 | 极快(无上下文切换) | 较慢(锁获取/释放开销) |
| 中竞争 | 较快(少量自旋) | 一般 |
| 高竞争 | 急剧下降(大量自旋) | 较优(阻塞+调度) |

结论 :CAS 适合低/中竞争 场景,高竞争下建议使用锁或 LongAdder 等分散竞争的方案。

6.2 JVM 对 CAS 的优化

  • 锁粗化:将多次 CAS 合并为一次

  • 锁消除:逃逸分析后确认无竞争时消除 CAS

  • 自适应自旋:根据历史成功率调整自旋次数

七、Java 原子包(java.util.concurrent.atomic)全景

java.util.concurrent.atomic 包提供了丰富原子类,底层均基于 CAS:

|----------------|------------------------------------------------------------------------------------|----------------|
| 分类 | 类名 | 说明 |
| 基础类型 | AtomicIntegerAtomicLongAtomicBoolean | 原子更新基础类型 |
| 数组 | AtomicIntegerArrayAtomicLongArrayAtomicReferenceArray | 原子更新数组元素 |
| 引用 | AtomicReferenceAtomicMarkableReferenceAtomicStampedReference | 原子更新引用类型 |
| 字段更新器 | AtomicIntegerFieldUpdaterAtomicLongFieldUpdaterAtomicReferenceFieldUpdater | 原子更新对象字段(基于反射) |
| 累加器(JDK 8) | LongAdderDoubleAdder | 高并发统计计数,分散竞争 |
| 累积器 | LongAccumulatorDoubleAccumulator | 自定义累积操作 |

7.1 AtomicReference:原子更新引用

java 复制代码
public class AtomicReferenceDemo {
    static class User {
        String name; int age;
        User(String name, int age) { this.name = name; this.age = age; }
    }
    
    public static void main(String[] args) {
        User oldUser = new User("old", 20);
        AtomicReference<User> ref = new AtomicReference<>(oldUser);
        
        User newUser = new User("new", 25);
        // CAS 更新引用
        ref.compareAndSet(oldUser, newUser);
        System.out.println(ref.get().name); // new
    }
}

7.2 AtomicIntegerFieldUpdater:轻量级字段更新

java 复制代码
public class FieldUpdaterDemo {
    static class Candidate {
        volatile int score;  // 必须 volatile
    }
    
    private static final AtomicIntegerFieldUpdater<Candidate> SCORE_UPDATER =
        AtomicIntegerFieldUpdater.newUpdater(Candidate.class, "score");
    
    public static void main(String[] args) {
        Candidate candidate = new Candidate();
        SCORE_UPDATER.incrementAndGet(candidate);  // 原子+1
        System.out.println(candidate.score);       // 1
    }
}

八、实战:基于 CAS 实现简易限流器

java 复制代码
package com.example.ratelimiter;

import java.util.concurrent.atomic.AtomicLong;

/**
 * 基于 CAS + 固定时间窗口的简易限流器
 * 
 * 设计目标:限制每秒最大请求数(固定时间窗口算法)
 * 
 * 核心原理:
 * - 使用 AtomicLong 作为计数器,保证原子性
 * - 使用 volatile 记录窗口起始时间,确保多线程可见性
 * - 使用双重检查锁(DCL)确保重置逻辑只执行一次
 * - 使用 CAS 自旋实现无锁递增
 * 
 * 注意:这是固定时间窗口(每1秒重置),不是真正的滑动窗口。
 * 固定窗口存在临界突发问题(如窗口边界处允许双倍流量),
 * 适合对流量均匀性要求不高的场景。
 * 
 * @author YourName
 */
public class FixedWindowRateLimiter {
    
    /**
     * 每秒允许的最大请求数(限流阈值)
     */
    private final long maxRequestsPerSecond;
    
    /**
     * 计数器:统计当前窗口内已通过的请求数
     * 使用 AtomicLong 保证原子性,避免多线程竞争
     */
    private final AtomicLong counter = new AtomicLong(0);
    
    /**
     * 当前窗口的起始时间戳(毫秒)
     * volatile 保证多线程间的可见性
     */
    private volatile long lastResetTime = System.currentTimeMillis();
    
    /**
     * 构造限流器
     * @param maxRequestsPerSecond 每秒最大请求数
     */
    public FixedWindowRateLimiter(long maxRequestsPerSecond) {
        this.maxRequestsPerSecond = maxRequestsPerSecond;
    }
    
    /**
     * 尝试获取令牌(是否允许通过)
     * 
     * 核心逻辑:
     * 1. 检查是否进入下一个时间窗口(距离上次重置 ≥ 1000ms)
     * 2. 如果是,使用双重检查锁重置计数器
     * 3. 使用 CAS 自旋尝试递增计数器
     * 4. 如果递增成功且未超过阈值,返回 true(允许通过)
     * 5. 否则返回 false(拒绝)
     * 
     * @return true 允许通过,false 被限流拒绝
     */
    public boolean tryAcquire() {
        long now = System.currentTimeMillis();
        
        // ===== 阶段1:检查是否需要重置窗口(双重检查锁) =====
        // 为什么用 DCL?既保证线程安全,又避免每次都要加锁带来的性能损耗
        if (now - lastResetTime >= 1000) {
            synchronized (this) {
                // 二次检查:防止多个线程同时进入同步块后重复重置
                if (now - lastResetTime >= 1000) {
                    counter.set(0);          // 重置计数器
                    lastResetTime = now;     // 更新窗口起始时间
                }
            }
        }
        
        // ===== 阶段2:CAS 自旋递增计数器 =====
        while (true) {
            long current = counter.get();
            
            // 如果当前计数已超过阈值,直接拒绝(不占用计数)
            if (current >= maxRequestsPerSecond) {
                return false;
            }
            
            // CAS 尝试将计数 +1
            if (counter.compareAndSet(current, current + 1)) {
                return true;  // 成功获取令牌
            }
            // CAS 失败,说明有其他线程抢先修改了计数器,重试
        }
    }
    
    /**
     * 获取当前窗口内的请求计数(用于监控)
     */
    public long getCurrentCount() {
        return counter.get();
    }
    
    /**
     * 获取当前窗口剩余时间(毫秒)
     */
    public long getRemainingMillis() {
        long now = System.currentTimeMillis();
        long elapsed = now - lastResetTime;
        return elapsed >= 1000 ? 0 : 1000 - elapsed;
    }
    
    // ==================== 测试入口 ====================
    
    public static void main(String[] args) throws InterruptedException {
        // 创建限流器:每秒最多 5 个请求
        FixedWindowRateLimiter limiter = new FixedWindowRateLimiter(5);
        
        System.out.println("=== 第1轮:连续请求10次(预期前5次通过,后5次拒绝) ===");
        for (int i = 0; i < 10; i++) {
            boolean allowed = limiter.tryAcquire();
            System.out.println("请求 " + i + ": " + (allowed ? "✅ 允许" : "❌ 拒绝"));
        }
        
        System.out.println("\n=== 等待1秒,窗口重置 ===");
        Thread.sleep(1000);
        
        System.out.println("=== 第2轮:重置后再请求(预期允许) ===");
        boolean allowed = limiter.tryAcquire();
        System.out.println("请求: " + (allowed ? "✅ 允许" : "❌ 拒绝"));
        
        System.out.println("\n当前计数: " + limiter.getCurrentCount() + 
                         ",剩余重置时间: " + limiter.getRemainingMillis() + "ms");
    }
}

输出:

请求 0: 允许 请求 1: 允许 ... 请求 5: 拒绝 请求 6: 拒绝 1秒后再次请求: 允许

九、注意事项与最佳实践

✅ 推荐做法

  1. 优先使用 java.util.concurrent.atomic 包提供的原子类 ,不要直接使用 Unsafe

  2. 低/中竞争 场景下使用 CAS,高竞争场景考虑 LongAdder 或锁

  3. 合理设置自旋上限,避免无限循环消耗 CPU

  4. 使用 AtomicStampedReference 解决 ABA 问题

  5. 利用 AtomicReference 实现无锁栈、无锁队列

十、总结

CAS 是 Java 并发包(java.util.concurrent)的基石 。从 AtomicIntegerConcurrentHashMap,从 ReentrantLock 的 AQS 到 LongAdder 的分段计数,处处都有 CAS 的身影。

|-------------|--------------------------------------------------------|
| 维度 | 要点 |
| 操作原语 | Compare-And-Swap:比较并交换,硬件级原子指令(CMPXCHG) |
| Java 实现 | Unsafe 类提供本地方法,Atomic* 类封装使用 |
| 核心优势 | 无锁、非阻塞、低开销(无上下文切换) |
| 主要问题 | ABA 问题、自旋开销、单变量限制 |
| 解决方案 | AtomicStampedReferenceLongAdderAtomicReference |
| 适用场景 | 计数器、状态标志、队列/栈实现、轻量级同步 |

一句话总结 :CAS 用 "失败重试" 替代了 "阻塞等待" ,在无锁状态下实现了线程安全的原子更新,是 Java 高并发性能的底层密码。理解 CAS,就等于拿到了理解 JUC 包的金钥匙。

相关推荐
泡海椒22 分钟前
JQuick-Curl 二次开发:自定义解析器、扩展点开发指南,如何把框架能力真正变成团队资产
java·开发语言·okhttp·maven
Allen_LVyingbo34 分钟前
医疗AI基础2026-构建可靠智能体的编程路径(上)
大数据·数据库·人工智能·python·自动化
青山木1 小时前
Hot 100 --- 数组中的第K个最大元素
java·数据结构·算法·排序算法
玛卡巴卡ldf1 小时前
【AICoding】笔试提示词设计思路
java·算法·ai编程
WWJA王文举1 小时前
FreeRTOS事件组和任务通知详解:为什么任务通知比队列更轻量?
开发语言·php
珍珠先生1 小时前
第10章 JDK 17 新特性:语法糖与新能力
java
Rnan-prince1 小时前
堆与TopK · 从零到通透 —— 9 节全系列复盘
python·算法
wuyk5552 小时前
12.归并排序:分治思想的稳定排序算法
开发语言·数据结构·算法·排序算法
吴声子夜歌2 小时前
ApacheCommons——commons-text(模板替换与文本算法)
java·开发语言·算法·apache
Haooog2 小时前
Agent 开发中的 Memory:State、短期记忆、长期记忆与 Memory Retrieval
java·agent·memory