JMM 进阶:彻底理解 CAS 实现原理

Java CAS 详解

CAS 是 Compare And Swap(比较并交换) 的缩写,是 Java 实现无锁并发 的核心原理,也是 java.util.concurrent.atomic 包(原子类)的底层实现机制,用来在不使用 synchronized 的情况下,保证多线程修改共享变量的线程安全

一、CAS 是什么

先比较内存中的值和预期值是否一致,如果一致,就把新值替换进去;如果不一致,说明有其他线程修改过,本次操作失败,不做替换

整个比较 + 替换 是一个原子操作(CPU 指令级保证,不可中断),所以多线程下不会出现数据错乱

二、CAS与乐观锁

乐观锁(Optimistic Lock)是一种并发控制思想

它的核心假设是:数据冲突很少发生

因此:

不加锁

先操作

提交时再检查是否有人修改过

如果发现冲突:

失败

重试

例如:

账户余额 = 100

线程A:

读取余额100

线程B:

读取余额100

A扣款:

100 → 90

提交成功

B也准备扣款:

100 → 80

此时检查发现:

余额已经不是100

说明:有人改过,操作失败

重新读取再计算

这就是乐观锁思想:先干活,最后检查

三、CAS 执行流程

CAS 操作需要 3 个值:

CAS(V, E, N)

V:内存中的实际值(Value)
E:期望值(Expected)
N:要更新的新值(New)

举例:

java 复制代码
AtomicInteger count = new AtomicInteger(10);

线程A执行:

java 复制代码
count.compareAndSet(10, 11);

内部过程:

当前值 = 10

期望值 = 10

新值 = 11

当前值10 == 期望值10

更新成功

结果:

java 复制代码
count = 11

如果线程B同时执行:

java 复制代码
count.compareAndSet(10, 12);

此时:

当前值 = 11

期望值 = 10

当前值11 != 期望值10

更新失败

返回:false

四、CAS 的底层实现

  1. Java 层面:sun.misc.Unsafe 类提供 CAS 方法
  2. 底层:CPU 原语指令 (如 cmpxchg),由硬件保证原子性
  3. 特点:无锁、轻量级、不会阻塞线程

Java 的 CAS 最终调用 JVM 的 Native 方法

例如:AtomicInteger

源码(JDK8):

java 复制代码
public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(
        this,
        valueOffset,
        expect,
        update);
}

这里调用:

java 复制代码
Unsafe.compareAndSwapInt()

JDK9以后:

java 复制代码
VarHandle.compareAndSet()

逐步替代 Unsafe

五、CAS 经典应用:原子类

Java 的 AtomicIntegerAtomicBooleanAtomicLong 全是基于 CAS 实现的

示例代码:

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

public class CASDemo {
    // 原子整数,线程安全
    private static AtomicInteger count = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) count.incrementAndGet(); // 底层就是 CAS
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) count.incrementAndGet();
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println(count.get()); // 一定输出 2000,线程安全
    }
}

incrementAndGet() 底层就是循环 CAS,直到修改成功

六、CAS 的优点

  1. 轻量级:不需要加锁、解锁,没有线程阻塞 / 唤醒开销
  2. 性能高:高并发、冲突少的场景,远优于 synchronized
  3. 无死锁:因为根本不加锁

七、CAS 的 3 大缺点

1. 自旋消耗 CPU(循环 CAS)

如果并发冲突严重,CAS 会一直循环重试,长时间占用 CPU

java 复制代码
// 底层伪代码
do {
    读取当前值 V;
    计算新值 B;
} while (!CAS(V, B));  // 失败就无限重试

2. 只能保证一个变量的原子操作

CAS 一次只能操作一个共享变量,无法像锁一样同时保证多个变量的原子性。(解决方案:AtomicReference 包装成对象)

3. ABA 问题

场景

  1. 内存值 V = A
  2. 线程 1 把 A → B
  3. 线程 2 又把 B → A
  4. 线程 3 执行 CAS:发现 V 还是 A,以为没被修改过,直接修改成功

问题:变量被修改过又改回来,CAS 无法感知

解决方案版本号机制AtomicStampedReference)每次修改都带上版本号,比较时不仅比值,还要比版本号

八、CAS vs synchronized

特性 CAS synchronized
实现 无锁,CPU 原子指令 阻塞锁,JVM 底层实现
原子性 单个变量 代码块 / 方法
阻塞 不阻塞线程 阻塞线程
性能 低冲突时极高 高冲突时更稳定
适用场景 原子变量、简单并发 复杂业务、多变量同步
相关推荐
名字还没想好☜1 小时前
Go 的 time.Ticker 陷阱:定时任务里被忽略的内存泄漏与正确关闭
java·数据库·golang·go·定时器
皓月斯语1 小时前
【C++基础】三目运算符
开发语言·数据结构·c++
音符犹如代码2 小时前
后端视角看 EventBus:发布订阅总线的原理、场景与用法
java·spring boot·guava
前端炒粉2 小时前
手撕小汇总
java·前端·javascript
初願致夕霞2 小时前
C++懒汉单例设计详解
服务器·开发语言·c++
脱胎换骨-军哥2 小时前
C++分布式系统设计:从通信引擎到分布式共识
开发语言·c++·分布式
唐青枫2 小时前
Java Kafka 实战指南:从 Topic、分区到 Spring Boot 可靠消息处理
java
脱胎换骨-军哥3 小时前
C++零成本抽象理论深度拆解:现代C++如何在不牺牲性能的前提下提供高级语法封装
java·开发语言·c++
lbb 小魔仙3 小时前
VS Code Python 高级调试技巧:从入门到精通
开发语言·python
我是唐青枫3 小时前
Java Netty 实战指南:从 NIO 线程模型到 TCP 编解码和心跳机制
java·tcp/ip·nio