ConcurrentHashMap 的 size() 返回近似值,适用于监控等低精度场景;需强一致性时应停写后遍历或用独立原子计数器;禁用 parallelStream 统计。

ConcurrentHashMap 的 size() 方法返回的是近似值,不是严格精确的实时总数。这是设计使然——它在高并发下牺牲一点精度,换取极高的读写性能。如果你需要“准确”统计,关键在于明确“准确”的定义:是强一致性(绝对精确到毫秒级),还是业务可接受的最终一致(例如统计任务执行期间无写入)?下面分场景说明可行方案。
直接调用 size() 适用于大多数业务场景
该方法内部会汇总 baseCount 和所有 CounterCell 中的计数值,结果反映的是调用瞬间的尽力而为的快照。在写操作不频繁或对精度要求不苛刻时(如监控大盘、日志采样),这就是最轻量、最推荐的方式。
- 无需加锁,无性能损耗
- JDK 8+ 已优化为 LongAdder 风格,冲突概率低,误差通常极小(尤其在中低并发下)
- 注意:若刚完成大批量 put/remove,而 counterCells 尚未完全刷新,
size()可能略滞后
需要强一致性总数?必须停写 + 全量遍历
唯一能获得「绝对准确」总数的方法,是确保统计过程中无任何写操作,并手动遍历所有桶(bin)。但这会带来严重代价:
- 需外部协调:暂停所有
put/remove线程(例如通过信号量或业务层灰度控制) - 遍历本身不加锁,但需容忍遍历时链表/红黑树结构可能被其他线程修改(JDK 8+ 遍历基于 volatile 读,能保证看到已提交的节点)
- 代码示例:
long count = map.entrySet().stream().count();—— 实际触发全表遍历,性能随数据量线性下降,千万级数据可能耗时数秒
替代方案:用原子计数器配合业务逻辑
如果业务本身对总数有强依赖(如库存校验、配额管理),更健壮的做法是不依赖 ConcurrentHashMap 自身计数**,而是单独维护一个 LongAdder 或 AtomicLong:
立即学习“Java免费学习笔记(深入)”;
- 每次
put(key, value)成功且是新增元素时,counter.increment() - 每次
remove(key)成功时,counter.decrement() - 该计数器与 map 操作组成原子逻辑(可用 CAS 或 synchronized 包裹),精度由业务控制
- 优势:读取
counter.longValue()是 O(1) 且绝对准确;缺点是增加编码复杂度和少量写开销
特别提醒:避免误用 parallelStream 统计
有人尝试用并行流:map.keySet().parallelStream().count()
这不仅不能提高准确性,反而因并发遍历可能重复计数或遗漏(ConcurrentHashMap 的迭代器是弱一致性,不保证遍历期间结构不变)。该方式既慢又不准,应杜绝。


















