
CAS 详解 场景示例CASCompare And Swap比较并交换是无锁乐观锁的原子操作CPU 硬件指令支持不是 Java 独有的概念Java 里Unsafe类提供 CAS 底层方法AtomicInteger等原子类就是基于 CAS 实现。一、核心原理CAS 包含 3 个参数V(内存地址上的实际值)A(预期旧值)B(要更新的新值)逻辑比较判断内存当前值V是否 预期旧值A如果相等 → 说明期间没有被别人修改把 V 更新成 B返回 true如果不相等 → 说明已经被其他线程改过更新失败返回 false不做修改一句话我认为现在的值是 A如果确实还是 A我就改成 B变了就放弃修改重试。⚠️ 重点CAS 是原子指令一条 CPU 指令完成比较 交换不会出现比较和赋值中间被线程切换打断的问题.所以不需要 synchronized 重量级锁属于乐观锁假设并发冲突概率不高不加锁冲突了重试。二、Java 中 CAS 底层入口sun.misc.Unsafe的 native 方法// var1对象var2字段偏移量var4预期值var5新值 public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);AtomicInteger.getAndIncrement()自增底层就是// 伪代码 do { A get(); // 拿到当前预期旧值A B A 1; // 新值B } while( ! compareAndSwapInt(this, offset, A, B) ); // CAS失败就循环重试三、CAS 的 3 大经典问题面试高频1. ABA 问题现象线程 1 读取值 A线程 2 把 A 改成 B再改回 A线程 1 执行 CAS 时发现还是 A认为没被修改成功更新但中间发生过修改。举例子初始值V AT1读取 VA准备 CAS此时时间片被抢走暂停T2把 V 从 A→B再把 V 从 B→AT1 恢复发现 VACAS 成功。T1 感知不到中间发生过变化解决方案版本号时间戳每次修改带上版本号比较的时候同时比较【值 版本】。AtomicStampedReference就是存(值, stamp版本号)解决 ABA。2. 循环自旋消耗 CPUCAS 失败会循环重试自旋如果并发极高大量线程一直 CAS 失败无限循环CPU 飙升。优化限制自旋次数、退化为重量级锁JDK 偏向锁 / 轻量级锁里的自适应自旋3. 只能保证一个变量原子性CAS 一次只能操作一个内存变量。如果要同时修改多个变量CAS 无能为力需要锁或者用AtomicReference封装成对象。四、场景示例场景 1计数器AtomicInteger最常用多线程累加统计比如接口请求计数不用synchronizedimport java.util.concurrent.atomic.AtomicInteger; public class CasDemo { // 原子计数器底层CAS static AtomicInteger count new AtomicInteger(0); public static void add() { // count 原子自增 count.getAndIncrement(); } public static void main(String[] args) throws InterruptedException { Thread t1 new Thread(() - { for (int i 0; i 5000; i) add(); }); Thread t2 new Thread(() - { for (int i 0; i 5000; i) add(); }); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(count.get()); // 结果一定10000线程安全 } }如果用普通int count不加锁大概率小于 10000.因为count是读 - 改 - 写三步会丢失更新而 CAS 原子操作保证线程安全。场景 2ABA 场景示例AtomicStampedReferenceimport java.util.concurrent.atomic.AtomicStampedReference; public class ABADemo { // 参数初始值初始版本号 static AtomicStampedReferenceInteger ref new AtomicStampedReference(100, 1); public static void main(String[] args) throws InterruptedException { Thread t1 new Thread(() - { int stamp ref.getStamp(); // 获取版本号1 Integer oldVal ref.getReference(); // old100 try { Thread.sleep(1000); // 休眠让t2修改 } catch (InterruptedException e) {e.printStackTrace();} // CAS预期值100新版本1同时校验版本号 boolean ok ref.compareAndSet(oldVal, 200, stamp, stamp1); System.out.println(t1 CAS结果 ok); }); Thread t2 new Thread(() - { int stamp ref.getStamp(); // 100 → 101版本1 ref.compareAndSet(100,101, stamp, stamp1); // 101 → 100版本再加1 ref.compareAndSet(101,100, ref.getStamp(), ref.getStamp()1); }); t1.start(); t2.start(); } }运行结果t1 CAS结果false虽然值变回 100但是版本号已经变成 3t1 持有的版本号还是 1CAS 失败成功规避 ABA。场景 3自己手写简易 CAS模拟注意下面只是伪代码模拟真实 CAS 必须 CPU 硬件指令Java 代码无法实现真正原子性// 模拟CAS逻辑 public boolean cas(int expect, int newValue){ if (this.value expect) { this.value newValue; return true; } return false; }这个模拟代码不是原子的比较和赋值中间可以被线程打断不是真正 CAS仅用来理解逻辑。五、CAS vs synchronizedCAS (乐观锁)synchronized (悲观锁)锁思想不加锁假设冲突少失败重试认为会冲突直接锁住底层CPU 原子指令OS 互斥锁JDK6 后有偏向 / 轻量锁底层也用到 CAS开销无内核态切换自旋高时 CPU 高竞争大时阻塞线程挂起唤醒开销大适用场景并发冲突少并发冲突激烈JDK6 之后 synchronized 优化偏向锁、轻量级锁底层就是用 CAS竞争激烈膨胀成重量级锁。六、业务上什么时候用 CAS✅ 适合读多写少并发冲突概率低接口 QPS 计数器、埋点统计简单状态标记比如「0 未初始化1 已初始化」防止重复初始化双重检查锁 DCL 也用到 CAS 思想并发容器ConcurrentHashMap更新节点、ConcurrentLinkedQueue无锁队列底层大量 CAS❌ 不适合高并发写、大量线程争抢同一个变量会大量自旋CPU 打满此时用 synchronized 或者锁更好。总结CAS 是比较并交换硬件原子指令实现乐观锁比较内存值和预期旧值相等则更新失败自旋重试存在 ABA、CPU 自旋、只能单变量原子性三个问题适合读多写少场景Atomic 原子类底层依赖 CAS。