news 2026/6/26 8:06:15

concurrent hashmap原理,扩容,扩容时怎么保证线程安全?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
concurrent hashmap原理,扩容,扩容时怎么保证线程安全?

面试官问题结构化回答:ConcurrentHashMap原理、扩容及扩容时的线程安全

核心总览

ConcurrentHashMap(CHM)是JUC包下为解决「HashMap线程不安全、Hashtable全表锁效率低」设计的并发安全哈希表,核心目标是「高并发下的线程安全 + 尽可能少的锁竞争」。其核心演进分为两个版本:

  • JDK1.7:基于「分段锁(Segment)」实现,锁粒度为「Segment数组的一个元素」,不同Segment的操作可并发;
  • JDK1.8:抛弃分段锁,采用「Node数组 + CAS + 局部synchronized(桶级锁) + 红黑树」,锁粒度细化到「单个哈希桶」,并通过CAS减少锁使用,性能远超1.7;

扩容是CHM的核心难点,1.8的扩容设计为「多线程协助扩容」(避免单线程扩容阻塞),线程安全依赖「扩容标识(sizeCtl)、CAS、synchronized、ForwardingNode占位」四层保障。

一、ConcurrentHashMap核心原理(1.7 vs 1.8)

1. JDK1.7 核心设计:分段锁(Segment + HashEntry)
结构层级作用线程安全保障
Segment数组(ReentrantLock子类)核心锁载体,CHM的外层数组,默认16个Segment每个Segment独立加锁,不同Segment的操作可并发(如操作Segment[0]和Segment[1]无锁竞争)
HashEntry数组(每个Segment内)存储实际键值对,结构同HashMap的Entry(key/value/next/hash)仅在操作同一Segment内的HashEntry时,需获取该Segment的锁
  • 核心逻辑:通过「两次哈希」定位元素——先哈希到Segment,再哈希到HashEntry;
  • 优点:锁粒度比Hashtable(全表锁)细,并发度=Segment数量(默认16);
  • 缺点:Segment数量固定,无法动态扩容;极端情况下(所有key哈希到同一Segment),退化为单锁,性能下降。
2. JDK1.8 核心设计:Node数组 + CAS + synchronized + 红黑树

彻底抛弃Segment,直接复用HashMap的「数组+链表+红黑树」结构,线程安全通过以下手段实现:

核心手段作用场景
CAS(无锁操作)初始化桶、插入第一个节点、更新size计数等无竞争场景,避免加锁
synchronized(桶级锁)桶内已有节点时,锁定「桶的头节点」(仅锁当前桶,其他桶可并发操作)
红黑树桶内链表长度≥8时转为红黑树,提升查询效率(O(n)→O(logn))
Node不可变普通Node的value被final修饰,仅能通过替换节点修改值,避免并发修改
扩容标识(sizeCtl)标记数组的状态(未初始化/正常/扩容中),避免多线程重复扩容
  • 核心逻辑:哈希直接定位到Node数组的桶,无分段锁;仅当操作「同一桶」时,才会加synchronized锁,锁粒度极致细化;
  • 核心改进:并发度不再受限(理论上=桶数量),且CAS减少了无竞争场景的锁开销,性能比1.7提升数倍。

二、ConcurrentHashMap扩容机制(1.7 vs 1.8,重点1.8)

扩容的核心目标是「扩大数组容量,减少哈希冲突」,CHM的扩容触发条件和流程随版本大幅优化:

1. JDK1.7 扩容逻辑(Segment内局部扩容)
  • 触发条件:单个Segment内的HashEntry数组容量达到「阈值(Segment容量×负载因子,默认0.75)」;
  • 扩容流程
    1. 获取当前Segment的锁(ReentrantLock),阻塞该Segment的所有操作;
    2. 将该Segment内的HashEntry数组扩容为2倍,重新哈希所有节点到新数组;
    3. 替换旧数组,释放锁;
  • 特点:仅扩容单个Segment,不影响其他Segment,但单Segment扩容时该Segment完全阻塞。
2. JDK1.8 扩容逻辑(全局数组扩容,多线程协助)

1.8的扩容是「全局扩容」(整个Node数组),设计为「多线程协助扩容」(避免单线程扩容耗时过长),核心流程如下:

(1)扩容触发条件
  • 「新增节点后」:putVal时,若当前桶的链表长度≥8且数组容量<64,先扩容数组(而非转红黑树);
  • 「容量达标后」:数组元素个数(size)≥「阈值(数组容量×负载因子,默认0.75)」,触发扩容;
  • 主动触发:调用putAll()resize()等方法时,直接触发扩容。
(2)扩容核心流程(分3阶段)
阶段操作细节核心控制(sizeCtl)
准备阶段1. 检查sizeCtl:若为负数,说明已有线程在扩容,当前线程协助扩容;
2. CAS将sizeCtl从「阈值」改为「-1」(标记扩容开始);
3. 创建新数组(容量=旧数组×2);
4. 将sizeCtl设置为「-(扩容线程数+1)」(默认-2,标识扩容中);
sizeCtl含义:
- 正数:下次扩容阈值;
- 0:初始状态;
- -1:正在初始化数组;
- 负数(≠-1):-(扩容线程数+1),如-2表示1个线程在扩容;
迁移阶段1. 遍历旧数组的每个桶(从后往前),分配给不同线程迁移;
2. 锁定当前桶(synchronized),避免迁移时并发修改;
3. 将桶内节点重新哈希到新数组(高位哈希判断归属);
4. 迁移完成后,在旧桶中放入「ForwardingNode(转发节点)」,标记该桶已迁移;
ForwardingNode的作用:引导后续读写操作直接访问新数组,避免操作旧桶;
完成阶段1. 所有桶迁移完成后,将新数组替换旧数组;
2. CAS将sizeCtl设置为「新数组容量×0.75」(新的扩容阈值);
3. 扩容结束,恢复正常状态;
sizeCtl从负数恢复为正数(新阈值);
(3)多线程协助扩容的逻辑
  • 每个线程负责迁移「一段连续的桶」(如线程1迁移015号桶,线程2迁移1631号桶);
  • 若线程A迁移到某桶时,发现该桶已被线程B迁移(有ForwardingNode),则跳过,继续迁移下一个桶;
  • 迁移过程中,新的读写请求会「先协助扩容,再执行自身逻辑」(如put时发现桶是ForwardingNode,先迁移一个桶,再插入数据)。

三、扩容时的线程安全保障(核心重点)

CHM扩容时的线程安全是面试高频考点,1.7和1.8的保障手段差异显著,重点讲1.8:

1. JDK1.7 扩容的线程安全:分段锁独占
  • 扩容前先获取Segment的ReentrantLock锁,该Segment的所有读写操作(put/get/remove)都会被阻塞,直到扩容完成释放锁;
  • 优点:简单粗暴,完全避免并发修改;缺点:单Segment扩容时,该Segment的操作全部阻塞,并发度低。
2. JDK1.8 扩容的线程安全:四层保障(核心)

1.8通过「无锁+锁+占位+协作」四层机制,既保证线程安全,又不影响并发性能:

(1)扩容标识(sizeCtl):避免重复扩容
  • sizeCtl是volatile变量,保证多线程可见性;
  • 扩容前先CAS修改sizeCtl为负数(标记扩容中),其他线程看到负数后,不会重复触发扩容,而是协助扩容;
  • 扩容过程中,sizeCtl的数值(如-2、-3)标识当前扩容线程数,避免线程冲突。
(2)CAS:无竞争场景的原子性
  • 初始化数组、修改sizeCtl、插入第一个节点等操作,通过CAS保证原子性(如CAS修改sizeCtl为-1,避免多线程同时初始化扩容);
  • 无锁操作减少了锁竞争,提升扩容效率。
(3)synchronized:桶级别的排他锁
  • 迁移某桶时,先锁定该桶的头节点(synchronized (f),f为桶的头节点),确保同一时间只有一个线程迁移该桶;
  • 锁粒度仅为「单个桶」,其他桶的迁移/读写操作可正常并发,不会阻塞。
(4)ForwardingNode:迁移完成的占位与引导
  • 某桶迁移完成后,旧桶中放入ForwardingNode(特殊节点,hash值为-1),标记该桶已迁移;
  • 后续读写操作遇到ForwardingNode时,会执行两个逻辑:
    • 读操作:直接到新数组中查询,避免读取旧桶的无效数据;
    • 写操作(put/remove):先协助扩容(迁移一个未处理的桶),再执行自身逻辑,加速扩容完成;
  • ForwardingNode避免了「旧桶数据被修改」和「读写操作访问无效数据」的问题。
(5)节点操作的原子性
  • 普通Node的value被final修饰,仅能通过「替换节点」(如CAS替换头节点、synchronized替换链表节点)修改值,避免扩容时并发修改节点内容;
  • 迁移节点时,通过「高位哈希(hash & newCap)」判断节点归属新数组的哪个桶,保证节点迁移的正确性。

四、总结(面试收尾金句)

  1. CHM的核心演进:1.7分段锁(Segment)→1.8 CAS+桶级synchronized,锁粒度更细,并发性能更高;
  2. 1.8扩容的核心设计:「多线程协助扩容」替代单线程扩容,通过sizeCtl控状态、ForwardingNode引导读写、synchronized锁桶,兼顾效率与安全;
  3. 扩容时的线程安全核心:1.7靠Segment独占锁,1.8靠「sizeCtl标识+CAS+桶级synchronized+ForwardingNode」,既避免重复扩容,又保证迁移过程中数据不被并发修改。
面试追问应对
  • 问:“CHM 1.8扩容时,get操作能正常执行吗?”
    答:可以。get操作是无锁的:若桶未迁移,直接读旧桶;若桶已迁移(有ForwardingNode),则到新数组读;若桶正在迁移(被synchronized锁定),get会自旋等待锁释放,不会阻塞(保证读操作的高并发)。
  • 问:“CHM 1.8为什么抛弃分段锁?”
    答:分段锁的并发度受限于Segment数量(默认16),且Segment是重量级锁(ReentrantLock);1.8的桶级synchronized(轻量级锁,偏向锁/自旋锁优化)+CAS,锁粒度更细,并发度理论上无上限,且无锁操作更多,性能更高。
  • 问:“CHM扩容时,put操作会阻塞吗?”
    答:不会完全阻塞。put操作遇到未迁移的桶,会锁定该桶执行插入;遇到ForwardingNode,会先协助扩容(迁移一个桶),再执行插入;仅当操作正在迁移的桶时,会短暂等待synchronized锁释放,整体仍保持高并发。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/23 19:34:33

告别代码冗余,Dify可视化工作流编辑的7大高阶技巧,你掌握几个?

第一章:告别代码冗余,Dify可视化工作流编辑的核心价值在AI应用开发中,传统编码方式常伴随大量重复逻辑与复杂依赖管理,导致开发效率低下且维护成本高昂。Dify的可视化工作流编辑器通过图形化界面重构开发流程,将原本需…

作者头像 李华
网站建设 2026/6/25 23:37:40

【AI视频分析进阶指南】:掌握相似度阈值,提升检索精度90%

第一章:视频帧字幕检索的相似度阈值 在视频内容分析中,通过提取关键帧并结合其对应字幕进行语义匹配,是实现精准检索的核心环节。其中,相似度阈值作为判断文本与视觉内容是否匹配的关键参数,直接影响检索结果的准确率与…

作者头像 李华
网站建设 2026/6/25 21:30:48

MySQL用户管理

MySQL用户管理 与Linux操作系统类似,MySQL也有超级用户好普通用户之分如果一个用户只需要访问MySQL中的某一个数据库,设置数据库中的某一个表,那么可以为其创建一个普通用户,并未该用户赋予对应的权限,而不让用户看到…

作者头像 李华
网站建设 2026/6/25 0:27:26

前后台一起部署,vite配置笔记base\build

场景: 当静态文件放置在后台的子包里,有很多个子包,不同子包的static里用自己单独的,前台打包默认的根路径就不行,所以需要配置base base: /robotUrl/,配置完后,打包后,启动地址和打包后的html会…

作者头像 李华
网站建设 2026/6/25 22:20:38

论面向服务的体系结构在系统集成中的应用

在数字化转型加速推进的当下,企业对办公自动化(OA)系统的集成性、扩展性和灵活性提出了更高要求。面向服务的体系结构(SOA)以其松耦合、服务复用、跨平台交互等核心特性,成为破解OA系统集成难题的关键技术架…

作者头像 李华
网站建设 2026/6/25 12:34:45

Dify重排序核心技术解析(20年经验总结的3大选型原则)

第一章:检索结果重排序的 Dify 算法选择在构建高效的检索增强生成(RAG)系统时,检索结果的排序质量直接影响最终回答的准确性。Dify 作为一款低代码 AI 应用开发平台,支持多种重排序(Re-ranking)…

作者头像 李华