面试知识库
进阶

原子类与LongAdder#

一句话答案#

原子类基于 CAS 实现无锁线程安全的单变量操作;高并发计数场景下 LongAdder 用分段累加(cell 数组)把竞争分散到多个槽位,吞吐远高于 AtomicLong。

核心要点

原子类四大家族(java.util.concurrent.atomic):

类别代表类用途
基本类型AtomicInteger / AtomicLong / AtomicBoolean原子地读改写单个数值/布尔
引用类型AtomicReference原子更新对象引用(多变量封装成对象)
带版本/标记的引用AtomicStampedReference(版本号)/ AtomicMarkableReference(布尔标记)解决 ABA 问题
数组类型AtomicIntegerArray / AtomicLongArray / AtomicReferenceArray原子更新数组某个下标
字段更新器AtomicIntegerFieldUpdater / AtomicReferenceFieldUpdater原子更新某对象的 volatile 字段(省内存,不必每个字段都包一个原子类对象)
累加器(JDK8+)LongAdder / LongAccumulator / DoubleAdder高并发计数/累加

AtomicInteger 的底层:

  • 内部一个 volatile int value,保证可见性
  • 自增 incrementAndGet() = 自旋 + CAS:读旧值 → CAS(旧值, 旧值+1),失败就重试
public final int getAndAddInt(Object o, long offset, int delta) {
    int v;
    do {
        v = getIntVolatile(o, offset);          // 读当前值
    } while (!compareAndSwapInt(o, offset, v, v + delta)); // 失败自旋
    return v;
}
java

LongAdder 为什么更快(核心考点):

  • AtomicLong 的痛点:高并发下所有线程 CAS 同一个 value,竞争激烈→CAS 大量失败→自旋空转,CPU 浪费
  • LongAdder 的思路——分段(striping)
    • 内部维护一个 base 字段 + 一个 Cell[] 数组(每个 Cell 是一个独立的计数单元)
    • 低竞争时直接 CAS base;竞争激烈时不同线程散列到不同 Cell,各改各的,把热点打散
    • sum() = base + 所有 Cell 之和
  • Cell 用 @Contended 注解做了缓存行填充,避免伪共享与缓存行
  • 代价:sum() 不是强一致的瞬时快照(求和期间可能有并发更新),且占用内存更多

AtomicLong vs LongAdder 选型:

维度AtomicLongLongAdder
竞争低✅ 够用,且能拿精确实时值差别不大
竞争高❌ CAS 频繁失败,吞吐骤降✅ 分段累加,吞吐高
取值精确性强一致sum() 是最终一致的近似值
适用序列号、需要 CAS 返回值的场景监控统计、QPS 计数等只增不读或读不敏感场景

LongAccumulator 是 LongAdder 的泛化:可传入自定义二元函数(如取最大值 Long::max),不止累加。

面试回答(2分钟版)

原子类在 java.util.concurrent.atomic 包下,分几大类:AtomicInteger/AtomicLong 这种基本类型的,AtomicReference 这种引用类型的,解决 ABA 的 AtomicStampedReference,数组类型的 AtomicIntegerArray,还有字段更新器和 JDK8 新增的 LongAdder。它们的底层都是 CAS:内部一个 volatile 变量保证可见性,自增就是一个自旋加 CAS 的循环,CAS 失败就重试。重点说一下 LongAdder 和 AtomicLong 的区别:AtomicLong 在高并发下所有线程都去 CAS 同一个变量,竞争激烈时大量 CAS 失败、自旋空转,吞吐会掉得很厉害。LongAdder 用了分段的思想,内部有一个 base 加一个 Cell 数组,竞争小的时候改 base,竞争大的时候不同线程散列到不同的 Cell 各改各的,把热点打散,最后求和的时候把 base 和所有 Cell 加起来;它还用 @Contended 做了缓存行填充避免伪共享。所以高并发计数统计的场景优先用 LongAdder,但它的 sum 不是强一致的瞬时值;如果需要精确实时值或者要用 CAS 的返回值,还是用 AtomicLong。

追问与易错

追问方向:

  • “AtomicInteger 的 incrementAndGet 怎么实现的?”→ 自旋 + CAS:读 volatile 旧值,CAS(旧值, 旧值+1),失败重试
  • “LongAdder 为什么比 AtomicLong 快?”→ 分段累加把单点竞争打散到 Cell 数组,减少 CAS 冲突
  • “LongAdder 的 sum() 准确吗?”→ 不是强一致快照,求和期间可能有并发写入,是最终一致的近似值
  • “字段更新器 AtomicIntegerFieldUpdater 有什么用?”→ 让普通对象的 volatile 字段支持原子操作,省去为每个字段创建原子类对象的内存开销
  • “原子类能解决 ABA 吗?”→ 普通的不能,要用带版本号的 AtomicStampedReference

易错点:

  • ❌ “LongAdder 任何场景都比 AtomicLong 好”——低竞争下优势不明显,且 LongAdder 取不到强一致实时值
  • ❌ “AtomicReference 能保证里面多个字段的原子性”——它保证的是引用替换的原子性,要整体替换新对象
  • ❌ “原子类是无锁所以一定快”——竞争激烈时自旋同样消耗 CPU,未必比锁强