首页 > 项目 > 当前页面

HashMap在什么情况下线程不安全?

2026-06-14 NEW个对象

📌 HashMap在什么情况下线程不安全?

✅ 核心结论:
HashMap本身没有任何同步机制,在多线程环境下同时执行put、remove、resize等操作时,可能出现数据丢失、覆盖、死循环、链表断裂、元素丢失、读取脏数据等问题。JDK1.7甚至可能出现CPU 100%的死循环问题,因此HashMap属于典型的线程不安全容器。

1️⃣ 问题背景

HashMap是Java开发中使用最广泛的数据结构之一,底层采用数组+链表(JDK1.7)或数组+链表+红黑树(JDK1.8)实现。

在单线程环境下,HashMap拥有非常优秀的查询性能,平均时间复杂度接近O(1)。

但是在高并发场景中,例如秒杀系统、订单系统、缓存系统、网关服务等业务中,如果多个线程同时操作HashMap,则可能产生严重问题。

很多面试官喜欢问:

  • HashMap为什么线程不安全?
  • JDK1.7为什么会形成死循环?
  • JDK1.8解决死循环了吗?
  • ConcurrentHashMap为什么安全?
  • HashMap会出现数据覆盖吗?

这些问题本质上都与HashMap内部结构和扩容机制有关。

2️⃣ 核心原理

HashMap内部维护一个Node数组。

table[0]
table[1]
table[2] → Node → Node → Node
table[3]
table[4]

当执行put时:

  • 计算Hash值
  • 定位数组下标
  • 插入链表或红黑树
  • 必要时触发扩容

整个过程并没有加锁。

因此多个线程可能同时修改同一个桶中的链表结构。

⚠️ HashMap所有操作都假设只有一个线程修改数据,一旦多个线程同时写入,就可能破坏内部结构。

3️⃣ 数据结构分析

数组结构

Node[] table

数组用于快速定位桶位置。

链表结构

Node {
  int hash;
  K key;
  V value;
  Node next;
}

哈希冲突时形成链表。

红黑树结构

JDK1.8开始,当链表长度超过8时会转为红黑树。

虽然优化了查询性能,但并没有解决线程安全问题。

4️⃣ 算法分析

场景一:并发put导致数据丢失

假设两个线程同时向同一个桶插入数据。

线程A:put(k1,v1)
线程B:put(k2,v2)

两个线程同时读取桶头节点。

随后分别修改next指针。

最终后写入的线程覆盖前面线程结果。

导致某个节点永久丢失。

💡 面试高频答案:HashMap并发put可能产生数据覆盖和数据丢失。

场景二:size统计错误

HashMap中的size字段并非原子变量。

多个线程同时执行:

size++

可能出现:

线程A读取100
线程B读取100
线程A写101
线程B写101

最终size应为102。

结果却变成101。

场景三:读取脏数据

线程A正在put。

线程B执行get。

此时链表尚未构建完成。

线程B可能读取到中间状态数据。

出现查询失败或返回null。

5️⃣ 执行流程

JDK1.7死循环形成过程

这是面试最经典的问题。

当HashMap扩容时会执行transfer操作。

oldTable → newTable

JDK1.7采用头插法迁移链表。

A → B → C

迁移后变成:

C → B → A

如果两个线程同时扩容:

线程A resize
线程B resize

可能形成:

A.next=B
B.next=A

最终形成环形链表。

⚠️ get操作遍历链表时将永远无法结束,CPU直接飙升至100%。

JDK1.8如何解决

JDK1.8放弃头插法。

采用尾插法迁移链表。

从根本上避免了环形链表产生。

但是:

  • 不会死循环
  • 依然线程不安全
  • 依然可能数据丢失
  • 依然可能覆盖数据

6️⃣ 实际案例

案例一:缓存中心

开发人员将HashMap作为本地缓存:

private static Map<String,Object> cache = new HashMap<>();

多个线程同时更新缓存。

结果部分缓存数据消失。

最终排查发现是HashMap并发写导致节点丢失。

案例二:订单系统

订单状态存储在HashMap中。

并发修改时出现:

  • 订单状态覆盖
  • 订单数量统计错误
  • 部分订单无法查询

最终替换为ConcurrentHashMap解决问题。

案例三:JDK1.7线上事故

某电商系统高峰期CPU持续100%。

线程Dump发现:

HashMap.get()

持续占用CPU。

最终确认扩容过程中形成环形链表。

7️⃣ 优缺点分析

HashMap优点

  • 查询效率高
  • 插入效率高
  • 实现简单
  • 内存占用低
  • 单线程性能优秀

HashMap缺点

  • 线程不安全
  • 扩容成本高
  • 并发写可能丢数据
  • JDK1.7可能形成死循环
  • 无法保证可见性

替代方案

方案 线程安全 性能
HashMap
Hashtable
ConcurrentHashMap

8️⃣ 面试常见问题

Q1:HashMap为什么线程不安全?

因为内部没有加锁,多线程同时修改数组、链表或红黑树结构时会产生竞态条件。

Q2:JDK1.7为什么会死循环?

扩容时采用头插法迁移链表,并发resize可能形成环形链表。

Q3:JDK1.8解决线程安全吗?

没有。JDK1.8仅解决死循环问题,依然可能数据丢失和覆盖。

Q4:ConcurrentHashMap为什么安全?

通过CAS、自旋、synchronized等机制保证并发安全。

Q5:HashMap的get操作安全吗?

多个线程同时只读通常没问题,但如果存在写线程,则依然可能读取到不一致数据。

Q6:HashMap什么时候最容易出问题?

高并发put、remove、resize场景最容易出现结构损坏和数据丢失。

9️⃣ 总结

✅ HashMap线程不安全主要体现在以下几个方面:

  • 并发put导致数据覆盖
  • 并发put导致节点丢失
  • size统计错误
  • 读取脏数据
  • JDK1.7扩容形成环形链表
  • CPU 100%死循环

面试中最经典的一句话总结是:

JDK1.7的HashMap在并发扩容时可能形成环形链表导致死循环;JDK1.8虽然通过尾插法解决了死循环问题,但HashMap本质上仍然不是线程安全容器,多线程场景应优先使用ConcurrentHashMap。

相关文章

NEW个对象 NEW个对象
JAVA是世界上最好的语言