LongAdder适用于高并发写、低频读且允许短暂误差的场景,如QPS统计、错误计数等;不适用于库存扣减等强一致场景;初始化需用final字段,更新用add()/increment(),读取仅用sum()。

直接用 LongAdder 替换 AtomicLong 不是简单改个类型,关键在于理解它“写分散、读合并”的行为差异,并匹配业务对一致性、读频次和内存的容忍度。
明确适用边界:不是所有计数都适合换
LongAdder 专为高并发写 + 低频读 + 允许短暂误差的场景设计。比如:
- 接口 QPS 统计、慢调用次数、错误率分母累加
- 日志采样计数、埋点上报汇总、监控仪表盘指标
- 不用于库存扣减、订单号生成、余额变更等强一致场景
如果业务要求每次读都必须精确到毫秒级实时值,或需支持 compareAndSet、reset、getAndIncrement 等原子操作,就别硬换——它不提供这些能力。
替换时三步走:初始化、更新、读取
正确姿势直接影响效果和稳定性:
-
初始化必须是 final 实例字段:避免被意外重赋值,也防止因延迟初始化逻辑未触发导致 cells 数组始终为空
✅ 推荐:private final LongAdder counter = new LongAdder();
❌ 避免:private LongAdder counter = null;或在方法里反复 new -
写操作统一用 add() 或 increment():它们内部自动路由到 base 或 cells,无需手动判断竞争状态
✅ 正确:counter.increment();或counter.add(5);
❌ 错误:counter.longValue() + 1(非原子,且绕过分段逻辑) -
读操作只用 sum():这是唯一语义明确的聚合方法;
longValue()是它的别名,但易误导人以为它是“当前值”快照
✅ 推荐:long total = counter.sum();
❌ 避免高频调用:for (int i = 0; i (每次遍历数组开销大)
警惕两个隐形陷阱
表面平滑,实则有细节容易踩坑:
- sum() 不是线程安全快照:它不阻塞写入,执行过程中其他线程可能正在更新 base 或某个 Cell。结果反映的是调用开始那一刻的近似总和,不是某一纳秒的精确切片。这对统计类场景完全可接受,但不能用于条件判断依据(如“总数超限就拒绝”)
- 内存占用随竞争动态增长:cells 数组默认从长度 2 开始,最多扩到 ≥ CPU 核数的最小 2 的幂(如 16 核机器最多 16 个 Cell)。每个 Cell 占约 24 字节(含 @Contended 填充),16 个就是近 400 字节。若服务部署在内存受限容器中,且实际并发远低于核数,反而比 AtomicLong 更重
过渡期建议:保留 AtomicLong 作兜底验证
上线初期可双写对比:
- 同一业务逻辑中,同时调用
atomicLong.incrementAndGet()和longAdder.increment() - 定期采样比对
atomicLong.get()与longAdder.sum()的差值(正常应极小,几毫秒内收敛) - 观察 GC 压力和 CPU 使用率变化——预期是写吞吐显著上升,sum 调用少时 CPU 自旋明显下降
确认数据偏差在业务容忍范围内(例如统计误差


















