longadder通过分段更新(cell数组)显著缓解高并发原子计数瓶颈,避免atomiclong单点cas竞争导致的cpu空转与吞吐塌方;其sum()返回近似和,适用于监控等容忍误差场景,非强一致替代方案。

LongAdder 能显著缓解高并发原子计数瓶颈,核心在于它用分段更新代替全局 CAS 竞争——不是所有线程抢同一个 value,而是各自往不同的 Cell 槽位写,冲突概率大幅下降。
为什么 AtomicLong 在高并发下会卡住
AtomicLong 的 incrementAndGet() 底层是 Unsafe.compareAndSwapLong(),所有线程都盯着同一块内存地址。100 个线程同时调用,大概率只有 1 个成功,其余 99 个立刻自旋重试;下一毫秒又来一轮……CPU 白白消耗在空转上,实测吞吐可能比 LongAdder 低 5–10 倍。
- 现象:线程堆栈里大量出现
getAndAddLong+compareAndSwapLong循环调用 - 本质:单点竞争 → CAS 失败率随线程数指数上升
- 代价:不是慢一点,而是 CPU 利用率虚高、实际吞吐塌方
LongAdder 是怎么靠 Cell 数组分散压力的
它内部维护一个 base 字段 + 一个 Cell[] 数组(初始为 null),线程通过自己的 threadLocalProbe 哈希值映射到某个 Cell 槽位,优先往那里累加。
- 没竞争时:直接
casBase,和 AtomicLong 行为一致,零额外开销 - 首次竞争触发:初始化
cells数组(默认长度 2),用getProbe() & (length-1)定位槽位 - 后续同一线程:复用相同槽位,避免跨槽争用;不同线程大概率落在不同槽位
- 扩容条件苛刻:仅当多个线程反复冲突且数组未满时才扩容,防止小流量下盲目开销
sum() 不是原子快照,但绝大多数监控场景根本不需要
sum() 方法遍历 cells 数组并累加 base,过程中其他线程仍在并发修改各 Cell.value ——结果是“近似和”,不是某一时刻的精确快照。
- 这不叫缺陷,是设计取舍:换来了写入吞吐的大幅提升
- QPS 统计、错误计数、PV/UV 累加等场景,毫秒级延迟下的误差完全可接受
- 若真需要强一致(比如金融对账),别用 LongAdder,改用
synchronized+ 普通long或更重的同步机制 -
sumThenReset()是“读+清零”原子操作,但清零本身不阻塞写入,仍可能漏掉中间插入的更新
容易被忽略的四个实际坑点
LongAdder 不是 AtomicLong 的无缝替换,这几个细节不注意就会出问题:
-
reset()不是原子清零:先逐个设Cell.value = 0,再设base = 0,中间可能被其他线程 add 进新值 - 不支持
decrementAndGet():只能调用add(-1),不能做条件递减(如“小于 100 才减”) - 长期低流量后突增并发:
cells可能仍为null或长度很小,首波竞争会触发初始化+扩容,带来短暂毛刺 - 内存开销真实存在:每个
Cell占 24 字节(含对象头、@Contended填充),128 个槽位就是约 3KB,不是完全无感
真正关键的是理解它的定位:为高吞吐写的计数器,不是为强一致性写的通用原子变量。用错场景,性能优势反而会变成隐蔽负担。











