ConcurrentHashMap的size()返回近似值,因其无锁遍历baseCount与counterCells数组累加,统计过程中put/remove并发执行,导致漏计或重复;它用空间换时间,保障高吞吐而非强一致性。

ConcurrentHashMap 的 size() 方法不追求精确,而是返回一个**近似总量**。它在动态更新(如并发 put/remove)过程中持续工作,但不会暂停写操作,因此统计结果反映的是某一瞬时的“快照估计”,而非事务级准确值。
为什么 size() 不锁表也能线程安全
它依赖两个核心变量协同工作:
- baseCount:volatile long 类型,记录无竞争场景下的计数变化,通过 CAS 原子更新
- counterCells 数组:当多个线程同时修改 baseCount 发生竞争时,各自写入专属的 CounterCell,分散计数压力
这种设计类似 LongAdder,用空间换时间,避免单点锁或全局遍历,保障高并发读写的吞吐。
size() 实际怎么算出来的
调用 size() 时,内部执行 sumCount(),逻辑很直接:
- 先取
baseCount的当前值 - 再遍历
counterCells数组,把每个非 null 元素的value累加进去 - 返回两者之和(转为 int,超限则截断为
Integer.MAX_VALUE)
整个过程无锁、无阻塞、不校验结构是否正在扩容——所以若统计中途发生迁移、删除或插入,就可能漏计或重复计数。
高并发下结果为何是“近似”的
关键在于“**统计与修改并行发生**”:
- 遍历 counterCells 时,某个 cell 可能刚被其他线程更新,也可能尚未被写入
- baseCount 和 counterCells 的读取不是原子组合操作,存在微小时间差
- 若 map 正在扩容,部分桶可能处于 ForwardingNode 状态,
size()不会跳过它们,但也不保证其内容已被完整迁移或清空
这就导致结果只能是最终一致的估算值,适合监控、日志、粗略判断,不适合强一致性校验。
需要真正准确的总数怎么办
官方不提供锁全表的精确 size,因为违背设计初衷。可行替代方案有:
- 业务层维护一个
AtomicLong,每次成功put()或remove()后同步增减(注意覆盖所有路径,包括 compute、merge 等衍生方法) - 使用
mappingCount()(JDK 8+),返回long类型,语义同size()但无 int 溢出限制,仍是近似值 - 在低峰期或可停写窗口,用外部协调机制暂停写入,再调用
size()采样——但这已脱离 ConcurrentHashMap 的本意
ConcurrentHashMap 的哲学始终是:让写和读飞起来,把统计让给更合适的工具。

















