原子类与 CAS
当我们需要对共享变量进行轻量级的线程安全更新时,原子类提供了比锁更高效的选择。它们基于 CPU 的 Compare-And-Swap(CAS)指令实现无锁更新,是 JUC 包的核心组件之一。
为什么需要原子类?
- 锁的开销:线程阻塞/唤醒涉及内核态切换;而原子类在多数情况下通过自旋快速完成。
- 高并发计数器:如 QPS 统计、连接数、ID 分配需要高吞吐。
- 无需复合逻辑:仅做加减、引用替换或位操作,没必要引入锁。
CAS 基本原理
boolean success = unsafe.compareAndSwapInt(obj, offset, expect, update);
- 读取内存中的旧值
expect - 比较旧值是否仍为
expect - 如果相等则写入
update,否则失败并重试(自旋)
缺点:可能遇到 ABA 问题、长时间自旋导致 CPU 消耗、只能保障单变量操作。
常见原子类速览
| 类 | 功能 | 常见场景 |
|---|---|---|
AtomicInteger / AtomicLong | 自增、自减、更新 | 计数器、限流 |
AtomicReference<V> | 原子更新引用 | 缓存、策略热更新 |
AtomicStampedReference<V> | 带版本号的引用,解决 ABA | 无锁栈/队列 |
AtomicMarkableReference<V> | 带布尔标记 | 逻辑删除 |
LongAdder / LongAccumulator | 分段累加,降低热点竞争 | 高频写计数 |
AtomicIntegerFieldUpdater<T> | 原子更新对象字段 | 组合对象状态 |
AtomicInteger 实战
class RateLimiter {
private final AtomicInteger current = new AtomicInteger(0);
private final int limit = 1000;
boolean tryAcquire() {
while (true) {
int old = current.get();
if (old >= limit) {
return false;
}
if (current.compareAndSet(old, old + 1)) {
return true;
}
}
}
void release() {
current.decrementAndGet();
}
}
面试提示:需要返回布尔值或执行条件更新时优先使用
compareAndSet,避免getAndIncrement超限后再回滚。
LongAdder:高并发计数器
- 通过
Cell[]将热点写操作分散到多个槽位,最后再合并总和。 - 适合写多读少的场景(读时需要遍历
cells,成本略高)。
LongAdder qps = new LongAdder();
qps.increment();
long total = qps.sum();
面试题:
LongAdder为什么比AtomicLong更快?——因为分段减少了 CAS 撞车概率,但在读多写少或需要精确控制时仍应使用AtomicLong。
解决 ABA:AtomicStampedReference
AtomicStampedReference<Integer> ref =
new AtomicStampedReference<>(100, 1);
int[] stampHolder = new int[1];
Integer value = ref.get(stampHolder);
boolean ok = ref.compareAndSet(
value, 120,
stampHolder[0], stampHolder[0] + 1
);
stamp类似版本号,每次更新都会自增。- 可用于无锁栈/队列防止“节点被多次回收再复用”。
原子字段更新器
在不改变对象结构的前提下为字段提供原子操作,常用于 Netty/Disruptor 等框架。
class Task {
volatile int state;
private static final AtomicIntegerFieldUpdater<Task> STATE_UPDATER =
AtomicIntegerFieldUpdater.newUpdater(Task.class, "state");
boolean finish() {
return STATE_UPDATER.compareAndSet(this, 0, 1);
}
}
- 字段必须是
volatile,且public/protected可见(或在同包)。 - 减少额外的包装对象,内存友好。
常见陷阱与建议
- 自旋开销:在竞争激烈时 CAS 会一直失败,应退化成锁或引入随机退避。
- 复合操作:多个变量需同时更新时仍要借助锁或使用
AtomicReference携带不可变对象。 - ABA 问题:涉及指针结构时必须结合版本号或使用
StampedReference。 - 异常处理:原子操作不抛
InterruptedException,需要调用方自行处理中断。
高频面试题
-
CAS 会导致什么问题?
ABA、自旋耗 CPU、只能针对单变量操作。可通过版本号、退避策略、锁组合解决。 -
如何选择 AtomicLong 与 LongAdder?
读多写少用AtomicLong(读取即时准确),写多读少用LongAdder(减少竞争)。 -
原子类能保证可见性吗?
可以,内部使用volatile+ CAS,因此读写具备可见性,但不保证复合操作的原子性。 -
AtomicReference 的典型应用?
运行时热更新配置、无锁链表/队列、实现对象状态机。 -
为什么字段更新器要求字段为 volatile?
确保可见性,并让 Unsafe 能直接定位内存偏移完成 CAS。
小结
- 原子类适合轻量级、高并发的计数或状态切换,不必为所有共享数据加锁。
- 异常复杂的操作仍建议使用锁或不可变对象,避免难以维护的 CAS 逻辑。
- 面试中多从“CAS 原理 + 典型类 + 方案选型”回答,效果最好。