LongAdder不能直接替换AtomicLong,因其sum()返回近似快照、非强一致,且无compareAndSet等条件操作方法;仅适用于写多读少、允许最终一致性的统计场景。

为什么直接替换会出问题:sum() 不是 get()
LongAdder 的 sum() 返回的是一个近似快照值,不是强一致读取。它遍历 cells 数组并累加所有非空 Cell 的 value,再加上 base。这个过程本身不加锁,中间可能有其他线程正在更新某个 Cell,所以结果可能“慢半拍”。
而 AtomicLong.get() 是瞬时、强一致的 volatile 读。
如果你代码里写了类似 if (counter.get() > 100) doSomething(); 这种条件判断,换成 if (counter.sum() > 100) 就可能漏判或误判——因为 sum() 不保证原子性,也不提供 compareAndSet 等条件操作。
哪些场景能安全替换:写多读少 + 只要总量不要精确时序
适合用 LongAdder 替代 AtomicLong 的典型场景包括:
• HTTP 请求计数(如 Prometheus 的 Counter)
• 日志采样频次统计(如每秒多少条 warn 日志)
• 异步任务成功/失败汇总(最终落库前聚合)
• 缓存命中/未命中次数统计
这些场景共同点是:写入极其频繁,读取相对稀疏;读取只要“够用”的近似值,不需要和某次写入严格同步。
初始化和扩容行为差异:低并发下反而略慢
LongAdder 在首次写入时只用 base,和 AtomicLong 行为一致;但一旦发生一次 CAS 失败,就会懒初始化 cells 数组(初始长度为 2),后续竞争加剧还会动态扩容(总是 2 的幂)。这意味着:
• 单线程或 2~3 线程压测时,LongAdder 可能比 AtomicLong 慢 5%~10%,多了 probe 计算、数组索引、null 判断等开销
• 不要为了“听起来高级”提前替换,先看真实线程竞争程度(比如通过 JFR 或 Arthas 观察 Unsafe.compareAndSwapLong 的失败率)
• 如果你用在定时批处理里(每分钟只读一次),且写入线程常年 ≤4,那换它基本没收益
必须避开的坑:没有 compareAndSet,不能做条件更新
LongAdder 完全不提供 compareAndSet()、weakCompareAndSet()、getAndIncrement() 等带“返回旧值”语义的方法。它的 API 只有:
• add(long x)
• increment()
• decrement()
• sum()
• longValue()(等价于 sum())
• reset()(清零,但不是原子的)
如果你需要“仅当当前值小于 N 时才加 1”,就必须退回 AtomicLong,或者自己封装逻辑(比如用 StampedLock 包一层,但这就失去无锁意义了)。
LongAdder 的设计目标从来就不是这个。

















