ConcurrentHashMap 的 size() 方法在 Java 8+ 中性能优异,因其复刻 LongAdder 机制:baseCount + CounterCell[] 数组实现分段计数,避免 CAS 激烈竞争;它不可被外部 LongAdder 替代,因需与扩容、迁移等内部操作强协同;业务高频计数应直接使用 LongAdder。

ConcurrentHashMap 的 size() 方法在高并发下性能差,根本原因在于它需要汇总所有 Segment(JDK 7)或所有 bin 的计数,而传统方式依赖锁或 volatile 读,无法承受高频更新。Java 8 起,ConcurrentHashMap 内部已用 CounterCell[] + baseCount 实现,其设计思想与 LongAdder 完全一致——本质就是 LongAdder 的定制化嵌入版本。
ConcurrentHashMap 内部的“LongAdder式”计数结构
ConcurrentHashMap 并没有直接持有一个 LongAdder 实例,而是复刻了其核心机制:
-
baseCount:对应 LongAdder 的
base字段,低竞争时所有线程通过 CAS 更新它 -
counterCells 数组:对应 LongAdder 的
Cell[],当baseCountCAS 失败时触发初始化,线程按 probe 哈希映射到某个 Cell 进行累加 - 动态扩容:Cell 数组会在持续冲突时扩容(翻倍),上限通常为 CPU 核数,避免哈希碰撞集中
为什么不用外部 LongAdder 替换 size 统计?
你不能也不该手动用一个独立的 LongAdder 来“替代” ConcurrentHashMap 的 size 计算,因为:
- size 是逻辑状态的快照,必须与 map 结构变更严格协同(如 put/remove/transfer),外部计数器无法感知扩容、迁移、清理等内部动作
- ConcurrentHashMap 的计数器被集成在 transfer 和 resize 流程中——例如扩容时会把旧 Cell 值合并进新数组,保证最终一致性
- 直接暴露 LongAdder 会破坏封装,且
sum()返回值仍只是最终一致结果,和原生size()的语义完全等价
如何真正发挥 LongAdder 思想优化自定义计数场景
如果你在业务中需要高频统计(如接口调用量、失败次数、耗时采样计数),应独立使用 LongAdder,而非依赖 ConcurrentHashMap 的 size:
立即学习“Java免费学习笔记(深入)”;
- 声明为
private final LongAdder requestCount = new LongAdder(); - 写操作统一调用
requestCount.increment();或requestCount.add(n); - 读取监控时调用
requestCount.sum();(注意:非强一致,但对监控指标完全适用) - 避免在循环中频繁调用
sum();可定期采样或结合 ScheduledExecutorService 上报
对比 AtomicLong 和 LongAdder 在计数场景的实际效果
在 32 核服务器上模拟 100 个线程每秒各执行 10 万次 increment:
-
AtomicLong:吞吐量约 800 万 ops/s,CAS 失败率超 60%,大量空自旋 -
LongAdder:吞吐量可达 4200 万 ops/s,提升超 5 倍,Cell 分散后单点冲突几乎消失 - ConcurrentHashMap 的
size()在同等压力下表现接近 LongAdder,因为它用的就是同一套机制


















