LongAdder比AtomicLong快的根本原因是减少竞争:它将单点竞争拆分为多Cell分散更新,高并发时吞吐量提升3~10倍;但sum()非O(1)且非强一致,适用于写多读少场景。

LongAdder 为什么比 AtomicLong 快?不是“更原子”,是更少竞争
根本原因不是 LongAdder 的操作更底层,而是它把“所有线程抢同一个 value”这个热点,拆成了“每个线程尽量去抢自己的 cell”。AtomicLong 的 incrementAndGet() 底层死循环调用 compareAndSwapLong(),1000 个线程全怼在同一个内存地址上,999 次失败自旋白耗 CPU;LongAdder 在高并发时自动扩容 cells 数组,让线程通过 getProbe() 和 hash & (n-1) 分散到不同 Cell 上,CAS 失败率断崖下降。
- 低并发(比如 1~2 个线程):LongAdder 和 AtomicLong 几乎没差别,甚至略慢一点——因为多了 probe 初始化和数组索引逻辑
- 中高并发(>8 线程持续更新):LongAdder 吞吐量通常是 AtomicLong 的 3~10 倍,实测在 100 线程压测下,
sum()耗时可能只多 5%~10%,但累加性能翻倍 - 注意:
sum()不是 O(1),而是 O(n),它要遍历整个cells数组再加base;如果频繁调用sum()(比如每毫秒都查一次计数),反而可能拖慢整体性能
什么时候该用 LongAdder,而不是 AtomicLong?
看场景,不看“听起来更高级”。LongAdder 是为「写多读少 + 高吞吐累加」而生的,不是 AtomicLong 的通用替代品。
- 适合:
request_count、error_total、限流器中的令牌发放计数、日志采样计数器等——你只关心“最终总和”,不依赖中间某次精确瞬时值 - 不适合:
sequence_id生成、库存扣减(必须严格顺序/不可超卖)、作为 CAS 条件变量(比如if (add.get() > 100) doX())——因为sum()返回的是近似快照,不是强一致视图 - 特别注意:LongAdder 不提供
compareAndSet()或weakCompareAndSet()方法,没法做条件更新;要用就得切回 AtomicLong 或自己封装
LongAdder 的 cells 数组怎么扩容?别手动干预
扩容完全由内部 retryUpdate() 和 casBase() 协同控制,开发者不能、也不该碰 cells 字段。它的扩容逻辑很克制:
- 初始无竞争:所有操作走
base字段,cells == null - 首次冲突:尝试
casBase()失败后,初始化长度为 2 的cells数组,并把当前线程 hash 到某个Cell - 后续扩容:当某个
Cell连续失败(默认 2 次)、且当前数组未满、且无其他线程正在扩容时,才触发翻倍扩容(2→4→8…最大到 CPU 核心数) - 缩容?不主动缩。数组大小只增不减,除非显式调用
reset()(清空所有 cell 并重置 base)
容易踩的坑:sum() 不是实时值,value 字段还不可见
很多人 debug 时直接打印 longAdder.toString() 或反射看 value 字段,结果发现是 0 或旧值——因为 LongAdder 根本没有 public 的 value 字段,它的状态分散在 base 和 cells 里,唯一正确读取方式只有 sum()。
立即学习“Java免费学习笔记(深入)”;
-
sum()是最终一致性读:返回的是当前所有已提交更新的近似和,但不保证包含“刚刚进入 CAS 循环但尚未成功”的那次操作 - 不要在循环里高频调用
sum()做判断,例如while (add.sum() ——这会把原本的写优化,变成读瓶颈 - 监控埋点时若需低延迟指标,建议用
sumThenReset()(JDK 1.8.0_261+),它原子性地返回并归零,避免 sum + reset 两步带来的中间态误差
真正难的不是理解分段,而是判断你的业务是否真的需要它——很多号称“高并发”的系统,QPS 还不到 200,硬上 LongAdder 反而增加维护复杂度。先压测,再换。



















