hashmap线程不安全的核心原因是缺乏内置同步机制,所有操作未加锁,导致多线程并发时出现数据覆盖、丢失、size错误及jdk 1.7中头插法引发的环形链表死循环。

HashMap线程不安全的核心原因
HashMap没有内置同步机制,所有操作(put、get、resize)都未加锁。多线程并发修改时,多个线程可能同时读写共享变量(如数组引用、链表节点next指针、size计数器、modCount),导致状态不一致。这不是“偶尔出错”,而是缺乏原子性和可见性保障的必然结果。
JDK 1.7 中死循环的形成过程
关键在于扩容时的头插法迁移和多线程竞争修改链表指针。假设旧桶中链表为 A → B → null,两个线程 A 和 B 同时触发 resize:
- 线程 A 读取 e = A,next = B;执行 e.next = newTable[i](此时为 null),再赋值 newTable[i] = A;随后被挂起
- 线程 B 完成整个迁移:先插入 B,再插入 A,结果是 newTable[i] = B,B.next = A,A.next = null
- 线程 A 恢复,继续处理 e = B;从主内存读取 B.next 得到 A,于是 next = A;再执行 B.next = newTable[i](即 B.next = B),newTable[i] = B
- 最终形成 B → A → B 的环形链表
后续调用 get() 遍历该桶时,会无限循环,CPU 占用飙升至 100%。
JDK 1.7 和 1.8 中的数据丢失与覆盖
即使不扩容,普通 put 也可能出问题:
- 数据覆盖(JDK 1.7/1.8 均存在):两个线程同时判断 tab[i] == null 成立,各自新建节点并赋值 tab[i],后写的覆盖先写的,丢失一次写入
- 数据丢失(JDK 1.7 特有):在头插法迁移过程中,若一个线程完成部分迁移后被中断,另一线程完成全部迁移,前者恢复时可能跳过某些节点,导致元素彻底消失
- size 计数错误(JDK 1.7/1.8):size++ 不是原子操作,多线程并发执行会导致最终 size 小于实际元素数
JDK 1.8 的改进与遗留问题
Java 8 改用尾插法迁移链表,避免了环形结构,死循环和部分数据丢失问题已修复。但以下问题依然存在:
- put 操作中对空桶的判断和赋值仍非原子,覆盖问题照旧
- resize 过程中,不同线程可能操作同一桶的不同节点,虽不环形,但可能造成部分节点迁移失败或重复迁移
- modCount 的递增与迭代器校验在并发下失效,ConcurrentModificationException 可能不抛出,或误抛
也就是说,JDK 1.8 的 HashMap 是“不会死循环”,但仍是“不安全”——它保不住你的数据。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











