concurrenthashmap 的 size() 返回近似值而非精确值,因其采用无锁的分段计数(countercell 数组 + basecount),遍历时不阻塞写操作,导致结果可能滞后于实际元素数量。

ConcurrentHashMap 的 size() 方法在并发环境下不保证强一致性,它返回的是一个近似值,可能滞后于实际元素数量。
为什么 size() 不是精确的
ConcurrentHashMap 为避免全局锁和频繁同步开销,内部采用分段计数(JDK 8+ 是基于 CounterCell 数组的伪分段 CAS 计数)。每个线程在增删元素时,会尝试更新自己的 CounterCell,失败则退化为更新 baseCount。调用 size() 时,会累加 baseCount 和所有活跃 counter 单元的值 —— 但这个过程不加锁,也不阻塞写操作,因此:
- 正在执行的 put/remove 可能尚未反映到计数器中
- 多个线程同时更新计数器时存在微小窗口期竞争
- 某些计数单元可能处于扩容、初始化或未被遍历状态
size() 的实际行为(JDK 8/11+)
源码中 size() 调用的是 sumCount():
- 先读取 volatile 的
baseCount - 再遍历
counterCells数组(该数组本身非固定大小,且部分槽位可能为空) - 对非空 cell 的 value 做 volatile 读并累加
- 整个过程无同步块,也不等待正在进行的修改完成
所以它本质是一个“快照式估算”,适用于监控、日志、启发式判断等对精度要求不高的场景。
需要精确数量怎么办
若业务逻辑**必须依赖准确元素个数**(例如做容量校验、事务性统计),不能直接依赖 size(),可考虑:
- 改用
mappingCount()(JDK 8+):语义同size(),但返回long类型,避免 int 溢出;仍不保证精确 - 手动加锁遍历:
synchronized(map) { return map.size(); }—— 会严重降低并发性能,仅限极低频、临界场景 - 维护外部原子计数器:每次 put/remove 同时更新一个
AtomicLong,需确保所有路径覆盖(包括 compute、merge 等衍生方法) - 用流式统计:
map.keySet().stream().count()—— 无锁但遍历开销大,且结果仍是某一时刻的快照,不比size()更准
日常使用建议
绝大多数场景下,应把 size() 当作轻量级估算工具:
- 用于触发扩容阈值判断?不用 —— ConcurrentHashMap 内部自有扩容机制,不依赖 public size()
- 用于日志打印“当前约有 N 个缓存项”?可以,误差通常在几个以内
- 用于 if (map.size() > 1000) 清理?谨慎 —— 可能漏清或误清,建议结合时间戳、LRU 或专用清理线程
- 用于单元测试断言?可用,但别写
assertEquals(5, map.size()),而应加等待或重试逻辑
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











