longadder更适合高并发计数,因其采用分槽+懒合并策略降低竞争;synchronized串行化、atomiclong cas自旋导致cpu空转;需正确初始化并用sum()获取值,避免高频调用。

为什么不用 synchronized 或 AtomicLong 做高并发计数
在 QPS 过万的场景下,synchronized 会串行化所有更新线程,AtomicLong.incrementAndGet() 虽无锁,但底层依赖 CAS 自旋,在多核争抢激烈时失败率飙升,导致大量 CPU 空转。实测中,16 核机器上 AtomicLong 的吞吐可能比 LongAdder 低 3–5 倍。
根本问题不是“锁不锁”,而是“所有线程挤在同一个变量上竞争”。分段锁(Striped Locking)和 LongAdder 都是为了解决这个瓶颈——把一个计数器拆成多个,让线程尽量各算各的。
-
LongAdder不是传统分段锁,它用“分槽 + 懒合并”策略:写操作分散到多个Cell数组槽位,读操作才汇总 - 它不保证强一致性(比如中间状态不可见),但对监控指标、统计类场景完全够用
- 初始化后不能替换或重置内部结构,所以不适合复用在生命周期不确定的对象里
LongAdder 的正确初始化与更新姿势
别直接 new,也别当普通字段随便赋值。它内部有延迟初始化逻辑,首次 add() 才真正分配 Cell 数组。
public class Counter {
private final LongAdder adder = new LongAdder(); // ✅ 推荐:final + 实例字段
public void recordRequest() {
adder.increment(); // ✅ 等价于 add(1)
}
public long getTotal() {
return adder.sum(); // ✅ 必须用 sum() 获取最终值
}
}
- 不要用
adder.longValue()—— 它只是sum()的别名,语义不清 - 避免在循环里高频调用
sum():每次调用都要遍历所有Cell并加总,开销不小 - 如果需要原子性地“加并取旧值”,
LongAdder不支持;得换回AtomicLong.getAndAdd()
什么时候该换回 AtomicLong 或自己实现分段锁
LongAdder 是空间换时间:每个 Cell 占 24 字节(JDK 8+),默认初始容量 1,扩容最多到 CPU 核心数级别。内存敏感或计数极低频时,它反而更重。
立即学习“Java免费学习笔记(深入)”;
- QPS AtomicLong 更省、更直观
- 需要实时精确值(比如库存扣减)→ 必须用
AtomicLong或synchronized,LongAdder的sum()可能滞后几毫秒 - 要支持 reset / set / compareAndSet →
LongAdder没这些方法,只能封装一层或换方案 - 已有基于
ReentrantLock的分段锁逻辑(比如按 key hash 分桶),且需复用锁逻辑做其他同步 → 别硬套LongAdder,维护成本更高
容易被忽略的陷阱:sum() 不是线程安全的“快照”
sum() 方法本身是线程安全的,但它不阻塞写操作。执行过程中,其他线程仍在往不同 Cell 写入,所以返回值只是调用瞬间的近似和。
- 如果你在 A 线程调
sum()得到 1000,B 线程立刻add(1),C 线程紧接着再sum(),结果可能是 1000、1001 或 1002 —— 取决于 B 的写是否已落到某个Cell并被 C 的遍历覆盖 - 没有“冻结计数器”的 API,所以无法做严格意义上的事务性读取
- 监控埋点建议每 5 秒调一次
sum()上报,而不是每请求都读,既降开销又平滑数据
真正的难点不在怎么写,而在于想清楚:你要的到底是“大致趋势”,还是“某一时刻的精确总数”。选错就白优化了。











