(18)《CAS》

2026-03-21
10
-
- 分钟
|

核心思想:乐观锁(假设冲突少,先操作再重试;冲突时自动重试,不阻塞线程)


1. CAS 基础原理

  • Compare and Swap(比较并交换)

    • 比较内存中某变量的当前值期望值,若相等则原子性地更新为新值(否则重试)。

    • 通俗比喻

      像抢红包:你看到红包余额是 100(期望值),抢到后发现余额变成 105(实际值),于是放弃当前操作(重试),再试一次。

  • 关键依赖

    • volatile:保证变量可见性(线程修改后立即写回主内存,其他线程能立刻看到)。

    • 为什么需要 volatile

      若不加 volatile,线程可能从本地缓存读旧值(如线程A修改了 count=1,但线程B的缓存仍是 count=0)。


2. 乐观锁 vs 悲观锁(重点!)

对比维度乐观锁(CAS)悲观锁(synchronized)
核心思想最乐观:假设冲突少,先操作,冲突时重试。最悲观:假设冲突多,先加锁,阻塞其他线程。
执行流程1. 读值 → 2. 计算新值 → 3. CAS更新(失败则重试)1. 加锁 → 2. 操作共享变量 → 3. 解锁(其他线程等待)
线程状态不阻塞(线程持续重试,CPU空转但不挂起)阻塞(线程等待锁释放,进入 WAITING 状态)
适用场景竞争不激烈(如多核CPU下轻量级操作)竞争激烈(如高并发写入,避免频繁重试)
效率影响竞争少 → 效率高(无锁开销);竞争多 → 效率低(频繁重试消耗CPU)竞争少 → 效率低(锁竞争开销大);竞争多 → 效率高(避免无效重试)
典型例子AtomicInteger.incrementAndGet()synchronized 修饰的 count++

💡 为什么 CAS 是“乐观”?
它默认“别人不会改我的数据”,即使改了也只重试一次(像抢票系统:先尝试下单,失败了再重试,不卡住)。
为什么 synchronized 是“悲观”?
它默认“别人会改我的数据”,所以先锁住数据(像图书馆借书:锁住书架,别人不能碰)。


3. CAS 底层实现:Unsafe 类

  • Unsafe:JVM 提供的底层工具类sun.misc.Unsafe),直接调用 CPU 指令(如 cmpxchg)。

  • 为什么高效?

    操作系统指令在 CPU 层级完成,无 JVM 介入,避免了线程切换开销(比 synchronized 快 2-5 倍)。

示例代码(Unsafe 使用,仅作原理演示,生产中用原子类)

import sun.misc.Unsafe;

public class CASUnsafeDemo {
    private volatile int value = 0;
    private static final Unsafe unsafe = getUnsafe(); // 获取Unsafe实例(需反射,非生产推荐)
    private static final long valueOffset = unsafe.objectFieldOffset(CASUnsafeDemo.class.getDeclaredField("value"));

    public static void main(String[] args) {
        CASUnsafeDemo demo = new CASUnsafeDemo();
        // 尝试将 value 从 0 更新为 1,失败则重试
        boolean success = demo.compareAndSet(0, 1);
        System.out.println("Update success: " + success); // 输出 true
    }

    // CAS 操作:比较当前值是否等于 expect,是则更新为 update
    public boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

    private static Unsafe getUnsafe() {
        try {
            java.lang.reflect.Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            return (Unsafe) field.get(null);
        } catch (Exception e) {
            throw new RuntimeException("Get Unsafe failed", e);
        }
    }
}

注释

  1. Unsafe 是 JVM 内部类,生产代码避免直接使用(需反射,不安全)。
  2. compareAndSwapInt 是底层 CAS 指令封装,保证原子性。

4. 原子操作类:JUC 中的 CAS 实现

  • 作用java.util.concurrent.atomic 包提供线程安全类(如 AtomicInteger),底层用 CAS + volatile

  • 为什么比 synchronized 好?

    无锁竞争,低延迟、高吞吐(适合高并发计数器、状态标记)。

示例代码(AtomicInteger 实际用法)

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicDemo {
    private AtomicInteger counter = new AtomicInteger(0); // 原子计数器

    public void increment() {
        // 线程安全自增:底层调用 CAS(无锁)
        counter.incrementAndGet(); 
    }

    public static void main(String[] args) {
        AtomicDemo demo = new AtomicDemo();
        // 模拟多线程
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> demo.increment()).start();
        }
        try { Thread.sleep(1000); } catch (InterruptedException e) {}
        System.out.println("Final count: " + demo.counter.get()); // 输出 1000(正确)
    }
}

注释

  1. incrementAndGet() 本质是 CAS 操作:

    int expect = counter.get(); 
    int update = expect + 1;
    counter.compareAndSet(expect, update); // 重试直到成功
    
  2. 无需 synchronized避免线程阻塞,适合高频操作。


关键总结

场景推荐方案原因
轻量级计数(竞争少)AtomicInteger无锁、低延迟(CAS + volatile)
高并发写入(竞争激烈)synchronized避免频繁重试,锁开销固定
需要复杂同步逻辑ReentrantLocksynchronized 更灵活

为什么 CAS 不是万能?

  • 竞争激烈时:重试次数爆炸 → CPU 空转 → 效率低于 synchronized
  • ABA 问题:值从 A→B→A,CAS 误认为未变(可用 AtomicStampedReference 解决)。
    JVM 设计哲学无锁(CAS)是“默认最优解”,但需根据场景选择。
评论交流

文章目录