concurrenthashmap 的 size() 返回近似值而非精确值,因其采用 basecount 与 countercell 数组分段计数,sumcount() 无锁快照累加时可能遗漏未刷入主存的更新、遭遇并发修改或数组扩容不一致。

ConcurrentHashMap 统计元素总数用的是 size() 方法,但它返回的不是精确值,而是一个近似值。这是设计上的主动取舍:为保障高并发读写性能,它放弃强一致性,换来了低开销和高吞吐。
size() 的底层机制:baseCount + CounterCell 数组
ConcurrentHashMap 不靠单个变量实时累加,而是采用“分段计数”策略:
- baseCount:一个 volatile long 类型的基础计数器,低并发时多数更新直接走这里
- CounterCell 数组:当多个线程频繁竞争更新 baseCount(CAS 失败)时,就懒加载创建该数组,每个线程尽量往不同槽位写自己的增量
- 数组长度始终是 2 的幂(如 2、4、8…),上限不超过 CPU 核心数,避免过度扩容浪费内存和遍历开销
sumCount() 是 size() 的实际执行逻辑
调用 size() 时,内部会执行 sumCount(),它只是对当前可见状态做一次无锁快照:
- 读取 volatile 的
baseCount - 遍历
counterCells数组,把所有非 null 的CounterCell.value累加进来 - 整个过程不阻塞写入,也不加锁 —— 所以可能漏掉正在写入但尚未刷到主存的计数
为什么不能保证精确?关键原因有三个
- 读取过程中,某个
CounterCell.value可能被其他线程同时修改,你读到的是旧值或新值,无法保证原子性 - 数组本身可能正处在扩容中,新旧槽位状态未完全同步,遍历时可能跳过部分有效单元
- 某些线程刚完成 CAS 更新
baseCount,但你的读操作已执行完毕,这部分增量就未被计入
需要精确总数时怎么办?
如果业务逻辑确实要求强一致的元素数量(比如事务校验、精确分页),不要依赖 size():
- 改用
mappingCount()—— 它和size()行为完全一致,也是近似值,只是返回类型为 long(避免 int 溢出),并不能提升精度 - 真正需要精确值的场景,应自行维护一个
AtomicLong计数器,在每次put()/remove()后同步增减(注意与 ConcurrentHashMap 操作的原子性配合) - 或者在低并发、可接受短暂停顿的前提下,用
entrySet().size()—— 它会遍历全部桶,但会触发全表扫描,性能差且仍可能因并发修改产生不一致视图
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











