longadder比atomiclong快的关键在于分散竞争:无冲突时操作base,冲突后自动分cell避免cas自旋,结合哈希定位、@contended防伪共享及懒加载机制,在高并发统计场景显著提升性能。

为什么LongAdder比AtomicLong快?关键在“不抢一个坑”
因为AtomicLong.incrementAndGet()所有线程都挤在同一个变量上CAS,失败就自旋重试——100个线程争1个值,99个白耗CPU;而LongAdder默认先写base,冲突后自动分出多个Cell,让线程各自写自己命中的槽位,把热点打散了。
- 无竞争时:所有操作走
base,和AtomicLong性能一致 - 出现CAS失败时:才懒加载
cells数组(初始长度2),后续按需扩容为2的幂次 - 线程定位槽位靠
threadLocalRandomProbe哈希,不是简单取模,减少哈希碰撞 - 每个
Cell加了@Contended注解,避免伪共享——同一缓存行不塞多个Cell.value
sum()不是实时快照,别拿它做强一致性判断
sum()只是遍历当前所有cells并加上base,但某个线程刚写进本地Cell、还没触发合并逻辑(比如retryUpdate未执行),这部分增量就不会被计入。它适合统计类场景,不适合控制流分支。
- 错误用法:
if (counter.sum() >= 1000) { triggerAlert(); }→ 可能漏报或延迟触发 - 正确思路:需要瞬时精确值,退回
AtomicLong;或用ReentrantLock+ 普通long -
counter.longValue()和sum()行为完全一样,别以为换方法就更“准” - 千万别用
counter.toString()做数值比较——它内部调的就是sum(),纯属多套一层
初始化和使用姿势不对,反而拖慢性能
很多人一上来就new LongAdder()然后扔进高并发循环,却忽略了它的“懒加载”特性在低并发下反而多了一层判断开销;或者误以为要手动预热cells数组。
- 低并发(比如单线程计数):直接用
AtomicLong更轻量,LongAdder多出的字段和分支判断是负优化 - 不要手动调
cells相关方法——这个数组完全由LongAdder内部管理,外部不可见也不可控 - 不需要“预分配”,
LongAdder会在首次竞争时自动创建cells,且扩容策略已优化(最多到CPU核心数级别) - 如果业务要求读多写少,且能接受几毫秒误差,
sum()可定期缓存(比如每秒一次),避免高频遍历
真实压测中要注意的三个细节
光看文档说“快很多”没用,实测时容易忽略JVM参数、线程模型和观测方式带来的干扰。
- 必须开
-XX:-RestrictContended(JDK8u20+默认开启,老版本需显式打开),否则@Contended无效,伪共享照旧 - 线程数太少(如
- 别只比
increment()吞吐,还要看sum()延迟:高写入频次下cells数组可能动态扩容,遍历开销微增,但远低于AtomicLong自旋退化
sum()那句注释里写的:“the sum is not necessarily atomic”。它不保证你调用那一刻所有线程的修改都已落地——这个边界模糊了,监控图表上的毛刺和告警延迟就都有了解释。











