longadder通过base+cells分段累加实现低竞争计数,适合高并发监控统计;concurrenthashmap用它替代全局size变量以避免写入竞争;sum()返回近似快照值,不保证强一致。

LongAdder 对 size 统计的优化,本质是把“单点高热更新”变成“多点分散写入”,专为高并发下的计数场景设计。它不追求每次读取都绝对精确,而是在写入吞吐和读取一致性之间做了务实取舍——适合监控、指标统计这类允许短暂误差但要求写入不卡顿的用途。
为什么 ConcurrentHashMap 的 size() 需要 LongAdder
ConcurrentHashMap 内部不维护一个实时更新的全局 size 变量,因为每次 put/remove 都去更新同一个 long 值会引发严重竞争。JDK 8 起改用 LongAdder 来累计元素变化:
- 每次成功插入或删除,调用
addCount(1L, binCount),底层委托给 LongAdder 的add(1) - size() 方法返回的是
sum(),即 base + 所有非 null Cell 的 value 总和 - 避免了在 put/remove 这类高频操作中引入锁或高失败率 CAS
LongAdder 如何实现低竞争累加
它的核心机制不是靠更强的原子指令,而是靠结构重组来减少冲突:
- base 字段:无竞争时所有线程直接 CAS 更新这个基础值,轻量高效
- cells 数组:一旦检测到竞争(比如 CAS 失败),就初始化 cells,并用当前线程的 probe 值哈希定位到某个 Cell 槽位
-
线程局部映射:通过
ThreadLocalRandom.getProbe()获取哈希码,再用位运算& (cells.length - 1)快速定位槽位,避免取模开销 - 动态扩容:cells 初始为 null,首次竞争建为长度 2;后续若某槽位持续争用,会尝试扩容(2→4→8…),直到竞争缓解
伪共享防护与内存布局
每个 Cell 实例都标注了 @Contended,这是关键细节:
- CPU 缓存行通常是 64 字节,若两个频繁更新的变量落在同一缓存行,会导致 false sharing(伪共享)——一个线程改 A,另一个线程读 B,也会触发整行失效和总线同步
- @Contended 让 JVM 自动在 Cell 前后填充字节,确保每个 Cell 独占一个缓存行
- 虽然增加内存占用(每个 Cell 实际占约 128 字节),但换来的是线程间真正无干扰的写入
sum() 的语义与使用提醒
调用 sum() 得到的是当前所有分片值的快照和,但它不是强一致的:
- 遍历 cells 数组过程是非原子的,期间可能有其他线程正在更新某个 Cell
- 结果反映的是“某一时刻的近似总量”,适用于监控大盘、限流阈值判断、日志采样计数等场景
- 不适合做精确事务控制(如库存扣减)、条件判断依赖严格相等的逻辑
- 如果需要强一致性,仍应回归 synchronized 或 ReentrantLock + 普通 long










