这是《JVM 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 volatile 提供可见性和有序性,CAS 提供无锁原子更新。两者是并发工具和高效计数器的基础,但也都有限制:volatile 不解决复合操作原子性,CAS 可能自旋、ABA 和缓存竞争。
11.1 volatile 语义
写:
volatile write
前面的读写不会重排到写之后
对其他线程可见
读:
volatile read
后面的读写不会重排到读之前
能看到最后一次 volatile 写
适用:
- 状态标志;
- 单次发布;
- 双重检查锁;
- 独立观察值;
- 配合 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));
风险:
- 高竞争下自旋耗 CPU;
- 缓存行竞争;
- 长时间失败导致延迟;
- 复杂无锁算法难以验证。
处理:
- 使用 LongAdder;
- 拆分热点;
- 使用锁;
- 批量更新;
- 用压测选择方案。
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);
}
}
组合原则:
- 状态转换用 CAS;
- 配置发布用 volatile 或不可变对象;
- 复合状态封装在一个不可变对象;
- 失败路径要恢复状态;
- 对外暴露清晰生命周期。
本章小结
volatile 解决可见性和有序性,CAS 解决单个变量的原子更新。它们不是锁的完全替代品;复杂状态和临界区仍需要锁、不可变对象或并发容器。高并发计数优先评估 LongAdder,生产队列必须有界。
思考题
- volatile 能否让
count++线程安全? - CAS 的三个操作数是什么?
- 什么是 ABA,何时需要关注?
- AtomicLong 和 LongAdder 有什么差异?
- 为什么生产系统不推荐无界队列?