ConcurrentHashMap 1.8+ 采用分桶协作扩容而非分段锁:通过sizeCtl状态机控制、transferIndex动态分配迁移任务、ForwardingNode标记已迁桶,多线程并发迁移桶并保证读写不阻塞。

ConcurrentHashMap 在 JDK 1.8+ 中已不再使用分段锁(Segment)机制,所以严格来说,“分段扩容”这个说法并不准确——它实际采用的是分批迁移 + 协作扩容,而非按 Segment 划分的独立扩容。这种设计在高并发下能显著降低单点瓶颈,同时保障数据一致性。
扩容不是“分段”,而是“分桶协作”
所谓“分段扩容”容易让人联想到 JDK 1.7 的 Segment 模型:每个 Segment 是一个独立子表,可单独扩容。但 JDK 1.8 彻底移除了 Segment,改用单数组(Node<k>[] table</k>)+ 桶级锁 + CAS 控制。扩容时:
- 整个哈希表共用一个新数组
nextTable,容量为原数组两倍; - 旧数组的每个桶(bin)被划分为若干迁移批次(默认每批处理至少 16 个桶);
- 多个线程通过竞争
sizeCtl和推进迁移指针(transferIndex),动态领取未迁移的桶区间,各自负责该区段的数据再哈希与搬移。
核心协作机制靠 sizeCtl 和 ForwardingNode
扩容状态和线程协同由两个关键机制支撑:
-
sizeCtl作为状态机:初始为正数(阈值),触发扩容后被设为负值,形如resizeStamp(n) ,既标识当前扩容容量,又记录参与线程数; -
ForwardingNode(哈希值 = -1):当某个桶完成迁移,原位置会被替换为该特殊节点,后续 get/put 线程遇到它,会主动协助迁移邻近桶,形成“扩散式协助”; - 线程在 put 或 helpTransfer 过程中检测到 ForwardingNode,就会加入 transfer 流程,无需等待主扩容线程完成。
迁移过程保证线程安全的关键细节
数据从旧数组搬到新数组不是原子整体操作,但每个桶的迁移是加锁或 CAS 保护的:
立即学习“Java免费学习笔记(深入)”;
- 对每个待迁移桶,先用
synchronized锁住其链表头节点(或红黑树根节点),避免并发修改; - 迁移时按节点 hash 值的低位是否为 0,决定其在新数组中的位置是
原索引还是原索引 + 旧容量(即“高位 bit 决定落点”); - 迁移完成后,原桶置为 ForwardingNode;其他线程访问该桶时,会直接跳转到新数组对应位置,读写不中断。
为什么这比“真分段”更高效?
取消 Segment 后,扩容粒度从“固定 16 段”变为“按需动态分片”,带来三点优势:
- 无 Segment 数量硬限制,大容量场景下不会因段数不足导致锁争用加剧;
- 小负载时无需预分配全部 Segment,内存更轻量;
- 扩容期间读操作几乎不受影响(查不到 ForwardingNode 就直接读原桶;查到了就跳转新数组),写操作也只锁单桶,吞吐更高。


















