LongAdder通过分段累加(base+cells[])分散CAS竞争,避免AtomicLong单点热点导致的高自旋、高CPU占用问题,适用于写多读少、无需强一致性的计数场景。

LongAdder 能显著缓解高并发计数下的 CAS 自旋瓶颈,但不是万能解药——它只在「写多读少、不要求强实时一致性」的场景下才比 AtomicLong 有明显优势。
为什么 AtomicLong 在高并发下会卡死在 CAS 自旋上
所有线程都在争同一个内存地址:unsafe.getAndAddLong(this, valueOffset, 1L)。一旦线程数超过 CPU 核心数(比如 32 线程跑在 8 核机器上),CAS 失败率陡增,失败后立即重试,CPU 占用拉满,吞吐却上不去。
典型现象包括:
- CPU 使用率持续 >90%,但 QPS 不随线程数线性增长
- jstack 看到大量线程堆栈停留在
AtomicLong.incrementAndGet内部的 do-while 循环里 - GC 日志正常,但响应延迟毛刺明显,且集中在计数逻辑附近
LongAdder 是怎么分散热点的
它把单点竞争拆成「base + cells[]」结构:初始只用 base 字段,等出现竞争时才懒加载 cells 数组,每个线程通过 ThreadLocalRandom.getProbe() 哈希映射到某个 Cell 槽位,只对那个槽做 CAS。
关键机制包括:
-
cells数组大小始终是 2 的幂(如 2、4、8…),下标计算用位运算:index = hash & (cells.length - 1),避免取模开销 -
Cell类加了@sun.misc.Contended注解,防止多个Cell实例落在同一缓存行导致伪共享 - 数组不一开始就分配,而是首次冲突时才扩容,节省内存;低并发时行为几乎和
AtomicLong一致
什么时候该切到 LongAdder,又有哪些坑
直接换掉 AtomicLong 并不总是安全。要注意以下几点:
- 读操作(
sum())是非原子的:它遍历所有cells并累加base,中间可能有其他线程正在更新某些Cell,所以返回的是“最终一致”的近似值,不适合做 if-else 判断(比如if (counter.sum() > 100) { ... }) - 没有
compareAndSet、decrementAndGet这类精确控制方法;如果业务需要减操作或条件更新,得自己同步封装,反而可能引入锁 - 内存占用更高:一个空
LongAdder对象约 24 字节,而AtomicLong是 16 字节;cells数组每增加一个Cell就多占 64 字节(含填充) - 不是所有 JVM 都默认开启
-XX:RestrictContended,若禁用了@Contended,伪共享风险回归,性能提升打折扣
实际替换建议与最小验证代码
别一上来就全局替换。先定位热点计数器(比如日志埋点、限流计数、监控指标),再做 AB 对比:
LongAdder counter = new LongAdder(); // 替代 AtomicLong.incrementAndGet() counter.increment(); <p>// 读取时注意语义 long current = counter.sum(); // 不是快照,是当前各槽之和
验证是否生效,重点看两点:
- 压测时 CPU user% 是否下降、QPS 是否更平稳
- 用 JFR 或 async-profiler 抓热点,确认
Unsafe.compareAndSwapLong调用频次是否大幅减少
真正容易被忽略的是:LongAdder 的价值不在“更快”,而在“更稳”——它让计数器这个基础设施不再成为系统瓶颈的放大器。但如果你的计数器要参与业务决策(比如熔断阈值判断),那它本身就设计错了,该换架构,而不是换原子类。


















