resizeStamp是根据当前table容量n生成的唯一扩容戳,高16位含容量标识、低16位为0,用于区分不同轮次扩容;其值由(Integer.numberOfLeadingZeros(n) | (1 << 15)) << 16计算得出。

ConcurrentHashMap 在 JDK 1.8 中采用分段扩容(即多线程协同扩容),扩容时通过一个全局的 `sizeCtl` 字段配合 `resizeStamp` 来协调多个线程参与扩容。其中,`resizeStamp` 本身**不直接决定扩容线程数**,而是用于生成 `sizeCtl` 的初始标记值,再由 `sizeCtl` 的符号位和低 16 位共同编码当前参与扩容的线程数。
resizeStamp 是什么?
`resizeStamp` 是一个辅助函数 `resizeStamp(int n)` 的返回值,作用是根据当前 table 容量 `n` 生成一个「扩容戳」,它是一个带符号的 int 值,高 16 位存储与容量相关的标识,低 16 位为 0:
- 计算方式:
Integer.numberOfLeadingZeros(n) | (1 << 15)—— 实际源码是:(Integer.numberOfLeadingZeros(n) | (1 << 15)) << 16 - 例如:当
n = 16(二进制 10000),numberOfLeadingZeros(16) = 27(int 共 32 位),则resizeStamp(16) = (27 | 32768) << 16 = 0x40006B00 - 关键点:这个值只跟当前 table 容量有关,且**唯一确定**,用于区分不同轮次的扩容(避免旧扩容状态干扰新扩容)
扩容线程数怎么从 sizeCtl 中读取?
真正记录并发扩容线程数的是 `sizeCtl` 字段。当开始扩容时,`sizeCtl` 被设为一个负数,其高 16 位是 `resizeStamp(n)`,低 16 位表示**已参与扩容的线程数加 1**(即 `rs
- 初始触发扩容时:
sizeCtl = resizeStamp(n) + 2(即 `rs - 后续线程加入时:执行
U.compareAndSetInt(this, SIZECTL, sc, sc + 1),使低 16 位递增 - 因此,当前参与扩容的线程数 =
(sizeCtl & 0xFFFF) − 1 - 最大允许并发线程数受 `NCPU` 限制,默认最多为
(NCPU >> 1)(即 CPU 核数的一半,但至少为 1),实际新增线程前会检查:(sc & 0xFFFF) < MAX_RESIZERS
为什么需要 resizeStamp?
主要解决两个问题:
立即学习“Java免费学习笔记(深入)”;
- 扩容轮次隔离:不同容量的扩容(如从 16→32、32→64)产生不同的 `resizeStamp`,确保 `sizeCtl` 高 16 位不同,避免线程误判正在扩容的 table 版本
- 防止重复初始化:只有第一个把 `sizeCtl` 设为 `resizeStamp(n) + 2` 的线程才算扩容发起者;其他线程看到 `sizeCtl` 已为负且高 16 位匹配当前 `resizeStamp`,才加入协助扩容,否则可能跳过或重试
- 若没有 `resizeStamp`,仅靠 `sizeCtl
典型流程中的 sizeCtl 变化示例
假设初始 table.length = 16,触发扩容:
- 调用
resizeStamp(16)→ 得到 `rs = 0x40006B00` - 首个线程 CAS 设置
sizeCtl = rs + 2 = 0x40006B02(此时线程数 = 2 − 1 = 1) - 第二个线程发现 `sizeCtl == 0x40006B02`,尝试 CAS:`sizeCtl = 0x40006B03`(线程数 = 3 − 1 = 2)
- 第 5 个线程加入后,`sizeCtl = 0x40006B06`,线程数 = 6 − 1 = 5
- 某个线程完成自己负责的桶迁移后,执行 `U.getAndAddInt(this, SIZECTL, -1)`,将低 16 位减 1;当低 16 位回到 2,说明只剩 1 个线程(即发起者),它负责收尾(更新 table、重置 sizeCtl)


















