JDK 1.7 HashMap多线程扩容死循环本质是头插法迁移链表时,两线程并发操作同一桶导致A↔B环形链表;JDK 1.8改用尾插法+独立链表拼接,避免成环但put仍非线程安全。

HashMap 多线程扩容死循环问题,本质是 JDK 1.7 中头插法 + 链表迁移 + 并发执行导致的结构性缺陷,JDK 1.8 已修复该机制,但依然不保证线程安全。
为什么 JDK 1.7 扩容会死循环
关键在 头插法迁移链表 和 多线程同时操作同一桶链表:
- 扩容时调用
transfer()方法,遍历旧链表,对每个节点用头插法插入新数组对应桶中 - 头插法会反转链表顺序:原链表 A→B→C,迁移后变成 C→B→A
- 若线程 A 挂起在
e.next = newTable[i];前,线程 B 完成整个扩容,则旧链表结构已被改写 - 线程 A 恢复后继续用已失效的
next引用操作,可能把 A 插到 B 后面,而 B 的 next 又指回 A,形成 A↔B 环形链表 - 后续
get()或iterator()遍历时,会在环中无限循环,CPU 占用飙到 100%
JDK 1.8 是怎么修好的
核心改动是把头插法换成 尾插法,并引入 红黑树支持:
- 迁移时保持原链表顺序:A→B→C 迁移后仍是 A→B→C,不会因并发导致指针错乱成环
- 使用
loHead/hiHead两个临时链表分别收集低位和高位节点,再整体拼接到新桶中 - 即使多个线程并发扩容,各线程操作的是独立局部链表,不会交叉修改同一节点的
next - 注意:修复的是“扩容死循环”,不是“线程安全”——
put()本身仍非原子,多线程写仍可能丢数据或覆盖
面试回答要点建议
别只说“用了 ConcurrentHashMap”,要体现底层理解:
- 先点明问题只存在于 JDK 1.7,JDK 1.8 已从机制上规避环形链表生成条件
- 用“头插法反转 → 并发挂起 → 引用错乱 → 环形链表 → get 死循环”逻辑链讲清楚因果
- 强调修复不等于线程安全:ConcurrentHashMap 才是真正为并发设计(CAS + synchronized 桶级锁)
- 顺带提一句:日常开发中,只要涉及多线程共享写,就该直接选 ConcurrentHashMap,而不是加锁包装 HashMap
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











