ConcurrentHashMap 的 size() 方法返回近似值而非精确统计,因为它不加锁遍历全部桶,仅累加 baseCount 与 volatile 的 CounterCell 数组值,期间可能遗漏或重复计数。

ConcurrentHashMap 的 size() 方法在高并发下不保证实时精确,它返回的是一个近似值。这是因为直接遍历所有 bin 并累加每个链表/红黑树的长度,在写操作频繁的场景下成本太高,且难以在不锁全表的前提下获得强一致性结果。
为什么 size() 不是精确统计?
ConcurrentHashMap 采用分段锁(JDK 7)或 CAS + volatile 协作(JDK 8+)实现并发安全,但 size() 并未对全部 segment 或全部 Node 数组加锁。它通过以下方式妥协性能与准确性:
- 只读取 baseCount 变量(记录无竞争时的计数变化)
- 再尝试读取每个 CounterCell(用于分散高并发 increment 操作的计数单元)的值
- 若读取过程中有结构变更(如扩容、rehash),可能跳过部分桶或重复计算,最终结果仅反映某一时刻的“快照估计”
size() 的实际统计流程(JDK 8+)
调用 size() 时,内部执行类似如下逻辑:
- 先获取 baseCount 的当前值
- 遍历 counterCells 数组,对每个非 null 的 cell.value 累加
- 如果 counterCells 正在扩容中,或读取不稳定,会触发一次 fullAddCount 尝试补偿,但仍不阻塞其他线程
- 最终返回 baseCount + 所有有效 CounterCell 值之和
这个过程无锁、无等待,但无法保证在统计期间没有 put/remove 发生——所以结果是“最终一致”的近似值,不是事务性快照。
需要精确计数怎么办?
若业务强依赖准确元素个数(如限流、校验),不应依赖 size(),可考虑:
- 使用外部原子计数器(如 AtomicInteger),在每次 put/remove 后同步增减(需确保所有路径都覆盖)
- 在低峰期或可控场景下,用 mappingCount()(JDK 8+,语义同 size,但返回 long,仍为近似)配合短暂暂停写入做采样
- 避免在核心路径频繁调用 size(),尤其不能用于循环终止条件(如 while(map.size()
ConcurrentHashMap 的设计哲学是:**写和读操作优先保障吞吐与响应,统计类方法让位于性能**。理解这一点,就能合理预期 size() 的行为。

















