longadder适用于高并发写、低频读且允许误差的场景,如qps统计、监控指标;不适用于库存扣减等强一致性需求。使用时需final初始化、仅用add()/increment()更新、仅用sum()读取,并避开new滥用、维度混用等四大陷阱。

直接把 AtomicLong 换成 LongAdder 不是改个类型那么简单,关键在理解它“写分散、读合并”的行为逻辑,并确认业务场景是否匹配它的设计边界。
先看它适合什么场景
LongAdder 专为高并发写 + 低频读 + 允许短暂误差而生。典型例子包括:
- 接口 QPS 统计、慢调用/错误次数累加
- 日志采样计数、埋点上报汇总、监控仪表盘指标
- PV/UV 粗略统计、限流器中的请求计数
它不适用于需要强一致性的操作,比如:
- 库存扣减、订单号生成、账户余额变更
- 依赖精确瞬时值做条件判断(如“总数超 100 就拒绝”)
- 需要
compareAndSet、getAndIncrement或reset原子语义的逻辑
替换三步:初始化、更新、读取
每一步都有明确规范,错一步就可能失效或引入隐患:
-
初始化必须是
final实例字段:例如private final LongAdder counter = new LongAdder();。避免在方法内反复 new,也别用非 final 引用,否则 cells 数组可能始终不初始化。 -
写操作只用
add()或increment():它们自动路由到 base 或某个 Cell,无需手动判断。不要写counter.longValue() + 1,这绕过分段机制,还破坏原子性。 -
读操作只用
sum():这是唯一语义清晰的聚合方法。longValue()是它的别名,但容易让人误以为是“当前快照”。高频循环里调用sum()开销大,应避免。
注意两个关键认知偏差
sum() 不是线程安全快照——它遍历 cells 数组时,其他线程仍在并发修改 base 或任意 Cell。结果是调用开始时刻的近似总和,不是某一纳秒的精确切片。这对监控完全可接受,但不能作为决策依据。
reset() 也不是原子清零:它先逐个设 Cell.value = 0,再设 base = 0,中间可能被其他线程 add 进新值。若需“读+清零”,可用 sumThenReset(),但它同样不阻塞写入,仍可能漏掉并发更新。
避开四个常见落地坑
- 别在每次请求中 new 一个
LongAdder,也别放在 request scope 或 ThreadLocal 里——那等于退化成单线程计数,白费分段设计。 - 按维度隔离计数(如按 API 路径)时,推荐用
ConcurrentHashMap<string longadder></string>缓存实例,key 为路径名。 - 长期低流量后突增并发,cells 可能仍为 null 或长度很小,首波竞争会触发初始化+扩容,带来短暂毛刺,需在压测中关注。
- 它不支持
decrementAndGet(),要减只能add(-1);也不支持条件递减(如“小于 100 才减”)。











