sizectl是concurrenthashmap的核心控制变量,复用为初始化标志、扩容阈值和并发线程数:负数小于-1表示扩容中,-1为初始化中,0为未初始化,正数为扩容阈值。

ConcurrentHashMap 的 sizeCtl 是一个核心控制变量,它复用多种语义:初始化标志、扩容阈值、并发扩容线程数等。理解它,就抓住了 ConcurrentHashMap 启动和扩容的“开关”。
sizeCtl 的四种关键状态含义
sizeCtl 是一个 volatile int,不同取值代表不同阶段:
-
负数且小于 -1:表示有线程正在扩容,-1 表示初始化中,其余负数(如 -2、-3)表示当前有 (n-1) 个线程参与扩容(实际为
(-sizeCtl) - 1) -
-1:哈希表正在初始化(
tab == null且正在调用initTable()) - 0:默认初始容量(即 16),表示尚未初始化,后续首次 put 会触发初始化
-
正数:表示下次扩容的阈值(即
capacity * loadFactor),类似 HashMap 的threshold;若表已初始化但未扩容,该值就是扩容门槛
初始化时 sizeCtl 如何协调多线程
多个线程同时首次 put,可能并发触发初始化。ConcurrentHashMap 利用 sizeCtl 的 CAS 实现“抢占式初始化”:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 线程检查
tab == null && sizeCtl == 0→ 尝试将sizeCtl设为-1(CAS) - 只有一个线程能成功设为 -1,它获得初始化权,执行
new Node[16]并赋值给table - 其他失败线程会自旋等待,直到看到
table != null或sizeCtl > 0(初始化完成)才继续 - 初始化完成后,
sizeCtl被设为12(16 × 0.75),作为首次扩容阈值
扩容时 sizeCtl 如何支持分段迁移与协作
当 put 发现元素数量超过 sizeCtl,触发扩容:tryPresize(size) 会尝试将 sizeCtl 设为负数以“预约”扩容:
- 先计算目标容量(向上取最近 2 的幂),再通过 CAS 将
sizeCtl设为-(resizeStamp(n) (高位存扩容标识戳,低位留作线程计数) - 首个线程设成功后,开始迁移;后续线程发现
sizeCtl ,则协助迁移:通过 CAS 将 <code>sizeCtl加 1(即更负),表示新增一个工作线程 - 每个线程负责迁移一段 hash 槽位(
transferIndex控制分片),迁移完再 CAS 减 1;当sizeCtl回到扩容戳本身(即无参与线程),说明扩容完成
为什么不用普通锁而用 sizeCtl + CAS?
这是为了极致减少同步开销:
- 初始化阶段避免 synchronized 块阻塞所有线程,只让一个线程建表,其余等待即可
- 扩容阶段允许多线程并行迁移不同桶,
sizeCtl只管“谁来干、干多少”,不锁整个表 - 所有状态变更都基于 volatile + CAS,无锁但保证可见性与原子性,契合高并发写场景
sizeCtl 不是配置项,而是运行时状态寄存器——它把初始化、扩容、协作这些复杂流程,压缩成一个整数的多态语义,是 ConcurrentHashMap “无锁化”设计的精妙缩影。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










