concurrenthashmap 的 addcount 方法采用三层策略降低计数竞争:第一层优先 cas 更新 basecount,低并发零开销;第二层冲突时用 threadlocalrandom 散列到 countercells 数组槽位,实现线程隔离;第三层按需扩容数组但上限为 cpu 核心数,避免过度消耗。

ConcurrentHashMap 的 addCount 方法通过分层尝试 + 槽位分流策略,把原本集中争抢一个变量的高冲突场景,拆解为“先试主路、再分小道、最后修路”的柔性计数机制。核心不是消除竞争,而是让竞争不卡死、不空转、不拖垮吞吐。
第一层:优先尝试 baseCount,低并发零开销
每次 put() 或 remove() 成功后,addCount 首先用 CAS 直接更新 baseCount:
- 成功 → 计数完成,不触发任何额外结构,线程无感知
- 失败 → 说明已有其他线程正在修改它,此时才进入后续逻辑
这个设计保证了单线程或两三个线程写入时,counterCells 始终为 null,完全避免数组分配和散列计算开销。
第二层:用 ThreadLocalRandom 散列到不同槽位
一旦 baseCount CAS 失败,addCount 就转向 counterCells 数组:
- 数组长度是 2 的幂(如 2、4、8…),用
probe & (length - 1)快速取模定位槽位 -
probe来自每个线程独有的ThreadLocalRandom.getProbe(),天然具备线程隔离性 - 不同线程大概率落到不同槽位,相当于把“抢一个保险柜”变成“每人开自己的抽屉”
即使两个线程偶然命中同一槽位,也只是单个 CounterCell.value 上的 CAS 冲突,影响范围被严格限制在单个 long 字段,不会波及其他线程。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
第三层:冲突时按需扩容,但有硬上限
当某线程发现目标槽位非空且 CAS 更新失败,就调用 fullAddCount 处理:
- 若数组未初始化,先创建长度为 2 的
counterCells - 若当前槽位为空,直接新建
CounterCell填入 - 若再次冲突(比如两个线程同时想填同一个空槽),则触发扩容:数组长度翻倍(2→4→8…)
- 扩容上限为 CPU 核心数(
NCPU),防止数组过大导致sumCount()遍历时缓存失效、耗时飙升
这种“懒扩容”只在真实发生竞争时才发生,避免预分配浪费内存,也规避了过度扩容带来的读性能下降。
为什么这能显著减少竞争损耗
传统单变量计数在高并发下会出现大量 CAS 自旋失败,CPU 时间白白消耗在等待上;而 addCount 的三层策略把压力分散了:
- 时间维度:失败后不重试 baseCount,立刻降级到数组路径,避免长时自旋
- 空间维度:用数组制造多个独立 CAS 点,把全局竞争转为局部竞争
- 资源维度:扩容受 NCPU 限制,确保统计操作本身不会成为新瓶颈
结果就是:32 个线程密集写入时,baseCount 更新次数可能趋近于 0,绝大多数计数都落在各自独立的 CounterCell 中,整体吞吐接近线性增长。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










