LongAdder比AtomicLong更快在于用空间换时间,将热点变量拆分为多个Cell以减少缓存行争用;适用于写多读少场景,但sum()非原子快照、不支持重置、非强一致。

LongAdder 为什么比 AtomicLong 更快
核心在于它用空间换时间,把单个热点变量拆成多个 Cell,写操作尽量分散到不同缓存行,避免多核 CPU 频繁争抢同一块缓存(False Sharing)。AtomicLong 的 incrementAndGet() 在高并发下会反复 CAS 失败重试,而 LongAdder 把累加压力分摊了。
实操建议:
- 只在「写多读少」场景用 LongAdder,比如统计请求量、错误数;如果需要强一致的实时值(如限流阈值判断),还是得回退到
AtomicLong -
sum()不是原子快照——它遍历所有Cell并加总,期间其他线程可能还在写,结果只是近似值 - 初始化后不支持 reset,要清零只能新建实例;别试图用
sumThenReset()做周期统计,它不保证线程安全地“先读再清”
LongAdder 的 add() 和 increment() 有啥区别
没本质区别。increment() 就是 add(1L) 的语法糖。真正影响行为的是底层策略:首次调用时尝试直接 CAS 更新 base 字段;失败后才初始化 cells 数组并哈希分配线程专属 Cell。
常见错误现象:
立即学习“Java免费学习笔记(深入)”;
- 压测初期吞吐上不去——因为 cells 还没扩容,所有线程挤在 base 上竞争,表现接近 AtomicLong
- 某些线程长期只更新自己的
Cell,导致sum()值偏低(尤其短时低并发测试) - 误以为
add(-1)是“减法”,其实它就是带符号加法,语义完全正确,但要注意业务逻辑是否允许负值累积
LongAdder 在 JDK 8+ 中的兼容性陷阱
它从 JDK 8 引入,JDK 7 及更早版本根本不存在这个类。如果你的项目需兼容老版本,不能简单替换 import,得做运行时判断或抽象封装。
性能与兼容性权衡:
- JDK 9+ 中
Striped64(LongAdder 父类)做了 cache line padding 优化,减少 False Sharing;JDK 8 的实现 padding 不够彻底,极端场景下仍有性能落差 - Android 环境(尤其旧版 ART)不包含
sun.misc.Unsafe的完整支持,LongAdder 可能抛NoClassDefFoundError或静默降级失效 - 不要在
finalize()或 shutdown hook 里调用sum()——cells 数组可能已被 GC 回收,返回值不可靠
什么时候不该用 LongAdder
它不是万能替代品。一旦需求涉及精确、即时、可预测的数值语义,就得警惕。
- 分布式 ID 生成器?不行——
add()不保证全局单调递增,不同 Cell 的更新无序 - 作为 while 循环的退出条件?危险——
sum() < 1000可能永远为真,因为其他线程刚写完还没被 sum 捕获 - 监控埋点中直接打印
sum()日志?注意日志输出本身可能阻塞,而 sum() 遍历 cells 是 O(n) 操作,n 是活跃线程数,高峰时可能拖慢整个日志线程 - 和
volatile long混用?别碰——LongAdder 内部不依赖 volatile 语义,混用会导致可见性错乱,读不到最新值
最常被忽略的一点:它的内存开销是动态增长的。每个活跃线程最多持有一个 Cell,而每个 Cell 至少占 64 字节(含 padding),线程数飙到几百时,光 cells 就吃掉几十 KB——这在内存敏感的嵌入式或函数计算场景里,比锁竞争更致命。










