ConcurrentHashMap扩容由多线程协作完成,不阻塞读写、不等待全部迁移完成、按需参与、通过ForwardingNode和volatile变量(transferIndex、sizeCtl)协调分片与线程调度。

ConcurrentHashMap 的扩容不是由单个线程“包干到底”,而是靠多线程动态协作完成的。核心在于:**不阻塞读写、不等待全部迁移完成、每个线程按需参与、用标记节点和状态变量协调分工**。
扩容触发时的协作起点
当某个线程执行 put 或 addCount 发现元素总数超过阈值(size > sizeCtl),它会尝试发起扩容。但不会直接独占整个过程,而是:
- 先检查是否已有其他线程正在扩容(通过
sizeCtl < 0判断) - 若没有,它负责初始化新表(
nextTable)、设置sizeCtl为负值(含扩容戳 + 协作线程计数)、分配首个迁移段 - 若已有扩容进行中,它不等待,而是立即调用
helpTransfer去协助迁移一部分桶
ForwardingNode 是协作的关键信标
迁移过程中,旧数组每个桶一旦开始迁移,就会被替换成一个 ForwardingNode(hash 值为 MOVED = -1)。这个节点不只是“占位符”,更是协作指令:
- 任何线程在
put、get或compute中遇到ForwardingNode,都会主动调用helpTransfer -
ForwardingNode持有对nextTable的引用,让协助线程能立刻定位到目标新表 - 它本质是“任务已分派”的信号,避免重复迁移或遗漏
任务分片与线程调度靠两个 volatile 变量驱动
扩容不是随机找桶搬,而是按段划分、原子推进:
立即学习“Java免费学习笔记(深入)”;
-
transferIndex:记录下一个待分配的桶索引(从高往低)。每次有线程来领任务,就用 CAS 尝试把该值减去步长(默认 16),成功者获得对应区间 -
sizeCtl:负值时高位是扩容戳(resizeStamp),低位是当前参与迁移的线程数。每新增一个协作者,就 CAS 自增;完成任务后自减 - 线程完成自己分到的桶后,会再次检查
transferIndex是否还有剩余,有则继续领取,否则退出
读写操作在扩容中如何保持可用
这是 ConcurrentHashMap 区别于 HashMap 扩容的关键优势:
-
读(
get)完全无锁无阻塞:遇到ForwardingNode,直接查nextTable;否则照常查原table—— 因为迁移是逐桶进行,未迁移桶数据始终一致 -
写(
put)自动融入迁移流:若发现目标桶是ForwardingNode,先协助搬完该桶再插入;若桶为空或可 CAS 插入,直接写入原表,不干扰迁移 -
迁移中的桶只锁自己:
transfer对每个桶加synchronized(锁的是该桶头节点),不影响其他桶的并发访问


















