跳到主要内容

原子类与 CAS

当我们需要对共享变量进行轻量级的线程安全更新时,原子类提供了比锁更高效的选择。它们基于 CPU 的 Compare-And-Swap(CAS)指令实现无锁更新,是 JUC 包的核心组件之一。


为什么需要原子类?

  • 锁的开销:线程阻塞/唤醒涉及内核态切换;而原子类在多数情况下通过自旋快速完成。
  • 高并发计数器:如 QPS 统计、连接数、ID 分配需要高吞吐。
  • 无需复合逻辑:仅做加减、引用替换或位操作,没必要引入锁。

CAS 基本原理

boolean success = unsafe.compareAndSwapInt(obj, offset, expect, update);
  1. 读取内存中的旧值 expect
  2. 比较旧值是否仍为 expect
  3. 如果相等则写入 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,需要调用方自行处理中断。

高频面试题

  1. CAS 会导致什么问题?
    ABA、自旋耗 CPU、只能针对单变量操作。可通过版本号、退避策略、锁组合解决。

  2. 如何选择 AtomicLong 与 LongAdder?
    读多写少用 AtomicLong(读取即时准确),写多读少用 LongAdder(减少竞争)。

  3. 原子类能保证可见性吗?
    可以,内部使用 volatile + CAS,因此读写具备可见性,但不保证复合操作的原子性。

  4. AtomicReference 的典型应用?
    运行时热更新配置、无锁链表/队列、实现对象状态机。

  5. 为什么字段更新器要求字段为 volatile?
    确保可见性,并让 Unsafe 能直接定位内存偏移完成 CAS。


小结

  • 原子类适合轻量级、高并发的计数或状态切换,不必为所有共享数据加锁。
  • 异常复杂的操作仍建议使用锁或不可变对象,避免难以维护的 CAS 逻辑。
  • 面试中多从“CAS 原理 + 典型类 + 方案选型”回答,效果最好。