CAS 深度解析:从硬件指令到源码实现的全面剖析
一、引言:并发编程的“原子性”难题
在多线程编程中,原子性是最基本也最棘手的问题之一。经典的i++操作看似一行代码,在底层却被拆分为“读取-修改-写入”三个步骤。多个线程同时执行时,会出现数据不一致的问题。
传统的解决方案是加锁(synchronized或ReentrantLock),但锁会带来线程上下文切换、阻塞唤醒的开销。在低竞争场景下,加锁的性能损失远大于操作本身。
有没有一种机制,既保证原子性,又避免锁的开销?
CAS(Compare-And-Swap,比较并交换)给出了答案。它是一种乐观锁技术,允许线程在不加锁的情况下安全地更新共享变量,是现代并发编程的基石。
CAS的核心思想:先读取变量的当前值,然后比较当前值是否与预期值一致,如果一致则交换为新值,否则失败重试。整个过程是硬件级别的原子操作。
二、什么是 CAS?
2.1 定义与操作原语
CAS 是一个原子操作,包含三个操作数:
V:要操作的内存位置(变量)
E:期望的旧值(Expected value)
N:要写入的新值(New value)
执行逻辑:读取 V 的当前值,与 E 比较,如果相等则将 V 更新为 N,否则什么都不做。无论成功与否,都返回 V 的旧值。
// 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 的关键方法
// 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 加载,普通应用无法直接调用。
// 通过反射获取 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 实现自定义原子计数器
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 成员变量与初始化
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() 源码
public final long getAndIncrement() { // 委托给 Unsafe 的 getAndAddLong return U.getAndAddLong(this, VALUE, 1L); }4.3 Unsafe.getAndAddLong() 源码
// 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; }核心逻辑:
getLongVolatile:读取变量当前值,带volatile语义(从主内存读取)compareAndSwapLong:通过 JNI 调用 CPU 指令执行 CAS自旋:如果 CAS 失败,循环重试直到成功
4.4 JNI 层面的实现(HotSpot 源码)
Unsafe.compareAndSwapLong最终调用的是 OpenJDK 中的 JVM 函数:
// 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:携带布尔标记位
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:
分类 | 类名 | 说明 |
基础类型 |
| 原子更新基础类型 |
数组 |
| 原子更新数组元素 |
引用 |
| 原子更新引用类型 |
字段更新器 |
| 原子更新对象字段(基于反射) |
累加器(JDK 8) |
| 高并发统计计数,分散竞争 |
累积器 |
| 自定义累积操作 |
7.1 AtomicReference:原子更新引用
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:轻量级字段更新
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 实现简易限流器
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秒后再次请求: 允许
九、注意事项与最佳实践
✅ 推荐做法
优先使用
java.util.concurrent.atomic包提供的原子类,不要直接使用Unsafe在低/中竞争场景下使用 CAS,高竞争场景考虑
LongAdder或锁合理设置自旋上限,避免无限循环消耗 CPU
使用
AtomicStampedReference解决 ABA 问题利用
AtomicReference实现无锁栈、无锁队列
十、总结
CAS 是 Java 并发包(java.util.concurrent)的基石。从AtomicInteger到ConcurrentHashMap,从ReentrantLock的 AQS 到LongAdder的分段计数,处处都有 CAS 的身影。
维度 | 要点 |
操作原语 | Compare-And-Swap:比较并交换,硬件级原子指令( |
Java 实现 |
|
核心优势 | 无锁、非阻塞、低开销(无上下文切换) |
主要问题 | ABA 问题、自旋开销、单变量限制 |
解决方案 |
|
适用场景 | 计数器、状态标志、队列/栈实现、轻量级同步 |
一句话总结:CAS 用“失败重试”替代了“阻塞等待”,在无锁状态下实现了线程安全的原子更新,是 Java 高并发性能的底层密码。理解 CAS,就等于拿到了理解 JUC 包的金钥匙。
