知识模块
☕ Java 知识模块
三、Java 并发编程
volatile 与 CAS

volatile 与 CAS

volatile 和 CAS 是 Java 并发编程中实现线程安全的轻量级机制,相比 synchronized 具有更低的性能开销。

volatile 关键字

三大特性

volatile 保证的是可见性有序性,不保证原子性

特性说明原理
可见性一个线程修改后,其他线程立即可见内存屏障 + 缓存一致性协议
有序性禁止指令重排序内存屏障
原子性❌ 不保证-

可见性示例

public class VisibilityDemo {
    // 不加 volatile,线程可能不会看到 running 的变化
    private volatile boolean running = true;
    
    public void stop() {
        running = false;
    }
    
    public void doWork() {
        while (running) {
            // 工作逻辑
        }
        System.out.println("停止工作");
    }
}

原理

  1. 写入 volatile 变量时,强制刷新到主内存
  2. 其他线程的工作内存中的副本失效
  3. 读取时必须从主内存重新加载

禁止重排序

volatile 通过内存屏障(Memory Barrier)防止指令重排序:

// 经典的单例双重检查锁定
public class Singleton {
    private static volatile Singleton instance;
    
    public static Singleton getInstance() {
        if (instance == null) {              // 第一次检查
            synchronized (Singleton.class) {
                if (instance == null) {      // 第二次检查
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

为什么需要 volatile?

new Singleton() 分为三步:

  1. 分配内存空间
  2. 初始化对象
  3. 将引用指向内存

如果没有 volatile,步骤 2 和 3 可能重排序,导致其他线程看到未初始化的对象。

内存屏障

JVM 在 volatile 变量操作前后插入内存屏障:

写操作:
StoreStore屏障 → volatile写 → StoreLoad屏障

读操作:
LoadLoad屏障 → volatile读 → LoadStore屏障

不适用场景

volatile 不适合复合操作:

// ❌ 错误:volatile 不能保证原子性
private volatile int count = 0;
public void increment() {
    count++;  // 读取-修改-写入,不是原子操作
}
 
// ✅ 正确:使用 Atomic 类
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
    count.incrementAndGet();  // 原子操作
}

CAS(Compare-And-Swap)

原理

CAS 是一种乐观锁机制,操作包含三个操作数:

  • V:内存值
  • E:预期值
  • N:新值

当且仅当 V == E 时,将 V 设置为 N,否则不做任何操作。

// CAS 伪代码
boolean cas(int[] memory, int expected, int newValue) {
    if (memory[0] == expected) {
        memory[0] = newValue;
        return true;
    }
    return false;
}

Java 中的实现

CAS 通过 Unsafe 类的本地方法实现:

public class AtomicInteger {
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private volatile int value;
    
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
}

底层依赖 CPU 的 cmpxchg 指令,是原子操作。

Atomic 原子类

Java 提供了一系列原子类,基于 CAS 实现:

类名说明
AtomicInteger整数原子类
AtomicLong长整型原子类
AtomicBoolean布尔原子类
AtomicReference引用类型原子类
AtomicIntegerArray整数数组原子类
LongAdder高性能累加器
AtomicInteger count = new AtomicInteger(0);
 
// 自增
count.incrementAndGet();  // i++
 
// 自减
count.decrementAndGet();  // i--
 
// CAS 操作
count.compareAndSet(0, 1);  // 如果当前值是0,设置为1
 
// 获取并更新
count.getAndUpdate(x -> x * 2);  // 原子地乘以2

ABA 问题

CAS 只检查值是否相等,无法检测值是否被修改过又改回来。

示例

初始值: A
线程1: 读取 A,准备改为 C
线程2: A → B → A(修改后又改回来)
线程1: CAS(A, C) 成功,但中间状态被忽略

解决方案:添加版本号

// AtomicStampedReference 解决 ABA 问题
AtomicStampedReference<Integer> ref = 
    new AtomicStampedReference<>(100, 0);  // 初始值100,版本号0
 
int[] stampHolder = new int[1];
Integer value = ref.get(stampHolder);  // 获取值和版本号
 
// CAS 时同时检查值和版本号
ref.compareAndSet(value, 101, stampHolder[0], stampHolder[0] + 1);

CAS vs synchronized

对比项CASsynchronized
锁类型乐观锁悲观锁
阻塞不阻塞,自旋重试阻塞等待
性能低竞争时性能好高竞争时更稳定
CPU 开销自旋消耗 CPU上下文切换开销
适用场景简单操作复杂临界区

LongAdder 高性能累加

JDK 8 引入 LongAdder,在高并发累加场景下性能优于 AtomicLong

原理:分散热点,使用多个 Cell 存储,最终求和。

LongAdder adder = new LongAdder();
 
// 累加(无返回值,性能更高)
adder.increment();
adder.add(10);
 
// 获取总和
long sum = adder.sum();

选择

  • 需要获取中间结果 → AtomicLong
  • 仅需最终结果 → LongAdder

常见面试题

Q1: volatile 能保证线程安全吗?

不能完全保证。volatile 只保证可见性和有序性,不保证原子性。对于简单的标志位场景可以保证线程安全,但对于复合操作(如 count++)不能保证。

Q2: CAS 有什么缺点?

  1. ABA 问题:可通过版本号解决
  2. 循环时间长开销大:自旋失败时持续消耗 CPU
  3. 只能保证一个共享变量的原子操作:多个变量需要加锁

Q3: 为什么 Atomic 类比 synchronized 性能好?

  • Atomic 基于 CAS 实现,不阻塞线程
  • synchronized 会阻塞线程,涉及上下文切换
  • 低竞争时 CAS 性能更好

Q4: i++ 是线程安全的吗?如何保证?

// ❌ 不安全
int i = 0;
i++;
 
// ✅ 方式一:synchronized
synchronized(lock) { i++; }
 
// ✅ 方式二:AtomicInteger
AtomicInteger i = new AtomicInteger(0);
i.incrementAndGet();
 
// ✅ 方式三:LongAdder(高并发累加)
LongAdder i = new LongAdder();
i.increment();

小结

  • volatile 保证可见性和有序性,不保证原子性
  • volatile 适用于状态标志、单例双重检查锁定
  • CAS 是乐观锁,基于 CPU 原子指令
  • Atomic 类基于 CAS 实现,适合简单原子操作
  • ABA 问题可用 AtomicStampedReference 解决
  • LongAdder 适合高并发累加场景