原子类与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;
}javaLongAdder 为什么更快(核心考点):
- AtomicLong 的痛点:高并发下所有线程 CAS 同一个 value,竞争激烈→CAS 大量失败→自旋空转,CPU 浪费
- LongAdder 的思路——分段(striping):
- 内部维护一个
base字段 + 一个Cell[]数组(每个 Cell 是一个独立的计数单元) - 低竞争时直接 CAS
base;竞争激烈时不同线程散列到不同 Cell,各改各的,把热点打散 sum()=base + 所有 Cell 之和
- 内部维护一个
- Cell 用
@Contended注解做了缓存行填充,避免伪共享与缓存行 - 代价:
sum()不是强一致的瞬时快照(求和期间可能有并发更新),且占用内存更多
AtomicLong vs LongAdder 选型:
| 维度 | AtomicLong | LongAdder |
|---|---|---|
| 竞争低 | ✅ 够用,且能拿精确实时值 | 差别不大 |
| 竞争高 | ❌ 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,未必比锁强