ConcurrentHashMap1.8 多线程扩容机制

ConcurrentHashMap 1.8 彻底重构了扩容逻辑,抛弃了 1.7 的分段锁 + 全表单线程扩容 ,采用无锁 + CAS + 细粒度锁 实现多线程并行扩容,把扩容性能瓶颈从 O (n) 压到极致,是高并发场景下的核心优化。

我用极简原理 + 核心流程 + 关键细节的方式,清晰拆解这套高效扩容机制:


一、核心设计:为什么 1.8 扩容能做到高效?

  1. 数据结构升级:数组 + 链表 / 红黑树,不再是 Segment 分段锁
  2. 扩容核心思想不阻塞写操作 + 多线程分摊迁移任务
  3. 关键标识 :用sizeCtl控制扩容状态、用transferIndex分配迁移桶
  4. 最小迁移单元一个桶(链表 / 红黑树),线程只锁当前桶,不锁全表

二、核心扩容变量(看懂这 4 个变量就懂了扩容)

变量 作用
sizeCtl 扩容状态控制:-1 = 正在初始化;<0 = 多线程扩容中(-N 代表 N 个线程);>0 = 下一次扩容阈值
transferIndex 待迁移桶的起始索引,线程通过 CAS 抢占迁移任务,保证不重复、不遗漏
nextTable 扩容后的新数组(容量是原数组 2 倍),扩容完成后替换原 table
fwd 节点(ForwardingNode) 标记当前桶已迁移完成,其他线程访问会被引导去新数组操作

三、多线程并行扩容完整流程(最核心)

1. 触发扩容

  • 元素个数达到阈值sizeCtl
  • 单个桶链表长度≥8 且数组长度 < 64
  • 写线程(put/remove)发现扩容中,自动协助扩容(核心:无专门扩容线程,业务线程兼职扩容)

2. 初始化扩容(第一个触发扩容的线程)

  1. CAS 修改sizeCtl-1,独占初始化权
  2. 创建nextTable(容量 = 原容量 ×2)
  3. 设置transferIndex=原容量(从最后一个桶往前迁移)
  4. 重置sizeCtl-(1 + 参与扩容的线程数),标记扩容开始

3. 多线程抢占迁移任务(并行核心)

  1. 所有业务线程(put/get/remove)发现扩容,都会加入协助扩容
  2. 线程通过CAS 修改 transferIndex,批量领取迁移桶(默认一次领取 16 个)
  3. 领取规则:从后往前,每个桶只被一个线程迁移,无竞争
  4. 所有线程分摊迁移任务,容量越大,并行效率越高

4. 单桶迁移逻辑(无锁 + 最小粒度锁)

  1. 线程获取当前桶的头节点,加 synchronized 锁(只锁头节点)
  2. 把当前桶的链表 / 红黑树拆分成 2 份
    • 低桶:hash & 原容量 == 0 → 留在新数组原位置
    • 高桶:hash & 原容量 != 0 → 迁移到「原位置 + 原容量」
  3. 迁移完成后,将原桶置为fwd 节点,标记完成
  4. 释放锁,继续迁移下一个桶

5. 扩容期间的读写操作(完全不阻塞)

  • 读操作 :遇到 fwd 节点,直接去nextTable读取,无锁
  • 写操作 :遇到 fwd 节点,协助扩容,再执行写入
  • 只有正在迁移的桶会加锁,其他桶完全并行

6. 扩容完成

  1. 所有桶迁移完毕
  2. table指向nextTable
  3. 重置sizeCtl为新阈值(新容量 ×0.75)
  4. 销毁临时变量,扩容结束

四、1.8 多线程扩容的 3 个革命性优化

1. 业务线程兼职扩容,无单独扩容线程

不用等待后台线程扩容,所有线程一起加速,容量越大越快。

2. 最小粒度锁,无全表阻塞

只锁迁移中的桶头节点,99% 的操作无锁并行,并发能力爆炸。

3. 迁移过程无阻塞读写

读操作无缝跳转新数组,写操作协助扩容,彻底解决 1.7 单线程扩容的性能瓶颈


五、对比 1.7:性能差距有多大?

特性 JDK 1.7 ConcurrentHashMap JDK 1.8 ConcurrentHashMap
扩容方式 单线程全表迁移,全程阻塞 多线程并行迁移,无全表阻塞
锁粒度 Segment 分段锁 桶头节点 synchronized + CAS
扩容性能 O (n),高并发严重卡顿 O (n/M),M = 参与扩容线程数
读写阻塞 扩容期间阻塞所有操作 扩容期间读写几乎无感知
相关推荐
一 乐42 分钟前
医院挂号|基于springboot + vue医院挂号管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·论文·毕设·医院挂号管理系统
浪客川1 小时前
【百例RUST - 010】字符串
开发语言·后端·rust
鱼鳞_1 小时前
Java学习笔记_Day29(异常)
java·笔记·学习
烟锁池塘柳01 小时前
一文讲透 C++ / Java 中方法重载(Overload)与方法重写(Override)在调用时机等方面的区别
java·c++·面向对象
一叶飘零_sweeeet1 小时前
深入拆解 Fork/Join 框架:核心原理、分治模型与参数调优实战
java·并发编程
云烟成雨TD1 小时前
Spring AI Alibaba 1.x 系列【23】短期记忆
java·人工智能·spring
摇滚侠1 小时前
帮我整理一份 IDEA 开发中常用快捷键
java·ide·intellij-idea
赵侃侃爱分享2 小时前
学完Python第一次写程序写了这个简单的计算器
开发语言·python
断眉的派大星2 小时前
# Python 魔术方法(魔法方法)超详细讲解
开发语言·python
2501_933329552 小时前
技术深度拆解:Infoseek舆情处置系统的全链路架构与核心实现
开发语言·人工智能·自然语言处理·架构