JVMNotes

第 11 章:volatile 与 CAS

zjc 于 2026-01-11 发布

这是《JVM 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 volatile 提供可见性和有序性,CAS 提供无锁原子更新。两者是并发工具和高效计数器的基础,但也都有限制:volatile 不解决复合操作原子性,CAS 可能自旋、ABA 和缓存竞争。

11.1 volatile 语义

写:

volatile write
  前面的读写不会重排到写之后
  对其他线程可见

读:

volatile read
  后面的读写不会重排到读之前
  能看到最后一次 volatile 写

适用:

  1. 状态标志;
  2. 单次发布;
  3. 双重检查锁;
  4. 独立观察值;
  5. 配合 CAS 的热点字段。

不适用:

private volatile int count;

count++; // 仍然不是原子操作

11.2 状态标志示例

public class Worker {
    private volatile boolean running = true;

    public void run() {
        while (running) {
            process();
        }
    }

    public void shutdown() {
        running = false;
    }
}

如果 running 不是 volatile,工作线程可能长期看不到修改。

更完整的停止方式:

private volatile boolean running = true;

public void run() {
    while (running && !Thread.currentThread().isInterrupted()) {
        try {
            process();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            break;
        }
    }
}

11.3 CAS 原理

CAS(Compare And Swap)包含三个操作数:

操作数 含义
location 内存位置
expected 预期旧值
new 新值

流程:

if current == expected
  -> write new
  -> return success
else
  -> return failure

Java 通过 Unsafe / VarHandle 提供底层能力,业务通常使用 java.util.concurrent.atomic

11.4 Atomic 使用

计数:

AtomicLong counter = new AtomicLong();

counter.incrementAndGet();
counter.updateAndGet(v -> v + 10);
counter.compareAndSet(0, 1);

引用:

AtomicReference<State> state = new AtomicReference<>(State.NEW);

state.compareAndSet(State.NEW, State.RUNNING);

字段:

AtomicIntegerFieldUpdater<Config> updater =
        AtomicIntegerFieldUpdater.newUpdater(Config.class, "version");
updater.incrementAndGet(config);

字段 updater 要求字段是 volatile int,且访问权限正确。

11.5 LongAdder 与 LongAccumulator

高并发计数时,AtomicLong 会在同一 value 上 CAS 竞争。

LongAdder counter = new LongAdder();
counter.increment();
counter.add(10);
long total = counter.sum();

LongAdder 将更新分散到多个 Cell,读取时汇总。适合写多读少的统计。

选择:

场景 工具
竞争低 AtomicLong
高并发计数、偶尔汇总 LongAdder
自定义聚合 LongAccumulator
需要精确单值 CAS AtomicLong

11.6 ABA 问题

ABA:

线程 1 看到 A
线程 2 把 A 改成 B,再改回 A
线程 1 CAS(A -> C) 成功
但中间状态已经被忽略

数值统计通常不关心 ABA;链表、无锁栈、资源版本等场景可能关心。

解决:

AtomicStampedReference<Integer> ref =
        new AtomicStampedReference<>(10, 1);

int[] stampHolder = new int[1];
Integer current = ref.get(stampHolder);
ref.compareAndSet(current, 20, stampHolder[0], stampHolder[0] + 1);

更常见做法是使用带版本号的对象或不可变状态。

11.7 CAS 自旋与开销

失败时通常重试:

int current;
int next;
do {
    current = value.get();
    next = current + 1;
} while (!value.compareAndSet(current, next));

风险:

  1. 高竞争下自旋耗 CPU;
  2. 缓存行竞争;
  3. 长时间失败导致延迟;
  4. 复杂无锁算法难以验证。

处理:

  1. 使用 LongAdder;
  2. 拆分热点;
  3. 使用锁;
  4. 批量更新;
  5. 用压测选择方案。

11.8 VarHandle

Java 9 后推荐使用 VarHandle 替代部分 Unsafe 用法。

public class Buffer {
    private volatile int size;

    private static final VarHandle SIZE;

    static {
        try {
            SIZE = MethodHandles.lookup()
                    .findVarHandle(Buffer.class, "size", int.class);
        } catch (ReflectiveOperationException e) {
            throw new ExceptionInInitializerError(e);
        }
    }

    public void setRelease(int value) {
        SIZE.setRelease(this, value);
    }

    public int getAcquire() {
        return (int) SIZE.getAcquire(this);
    }
}

普通业务不需要手写 VarHandle;了解它有助于阅读 JDK 并发实现。

11.9 无锁队列与并发容器

常见容器:

容器 特点
ConcurrentLinkedQueue 非阻塞无界队列
ArrayBlockingQueue 有界阻塞
LinkedBlockingQueue 可选容量链表队列
SynchronousQueue 直接交接
DelayQueue 延迟元素
ConcurrentLinkedQueue 高并发吞吐

无界队列会掩盖背压问题:

生产速率 > 消费速率
  -> 队列无限增长
     -> heap OOM

生产系统应使用有界队列和明确拒绝策略。

11.10 volatile 与 CAS 的组合

public class CasState {
    private final AtomicReference<State> state =
            new AtomicReference<>(State.NEW);
    private volatile Config config;

    public void start(Config newConfig) {
        if (!state.compareAndSet(State.NEW, State.STARTING)) {
            throw new IllegalStateException("state=" + state.get());
        }
        config = newConfig;
        state.set(State.RUNNING);
    }
}

组合原则:

  1. 状态转换用 CAS;
  2. 配置发布用 volatile 或不可变对象;
  3. 复合状态封装在一个不可变对象;
  4. 失败路径要恢复状态;
  5. 对外暴露清晰生命周期。

本章小结

volatile 解决可见性和有序性,CAS 解决单个变量的原子更新。它们不是锁的完全替代品;复杂状态和临界区仍需要锁、不可变对象或并发容器。高并发计数优先评估 LongAdder,生产队列必须有界。

思考题

  1. volatile 能否让 count++ 线程安全?
  2. CAS 的三个操作数是什么?
  3. 什么是 ABA,何时需要关注?
  4. AtomicLong 和 LongAdder 有什么差异?
  5. 为什么生产系统不推荐无界队列?