发布于2026-07-10 阅读(0)
扫一扫,手机访问
HashMap 可以说是 Ja va 开发中间出镜率最高的键值对集合之一,面试十有八九也会问到它。单线程用起来那叫一个顺手,可一旦放到多线程的并发场景里,事情就坏了——HashMap 是绝对线程不安全的,强行使用的话,数据覆盖、死循环、数据丢失,一样都跑不了。
这篇文章会从结论、源码分析、五个并发不安全的典型场景、可视化流程图,以及 JDK1.7 和 1.8 的差异这几个面,把 HashMap 线程不安全的底裤彻底扒干净,帮你避开那些在并发下踩过的坑。
直接上结论:HashMap 不是线程安全的集合,这一点毋庸置疑。
Note that this implementation is not synchronized.要是非要在多线程环境下用键值对集合,那这几个方案可以参考:
说白了,根本原因就一句话:HashMap 里所有修改数据的操作——put、remove、resize——都没有加锁。多线程并发修改共享变量(数组、链表、红黑树),数据一致性自然就崩了。
咱们直接看源码,HashMap 的 put 方法和 resize 扩容方法,全程无锁:
// JDK1.8 HashMap.put() 核心方法,无任何锁修饰
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
// 扩容方法,同样无锁
final Node[] resize() {
// 扩容逻辑...
}
多线程同时操作同一个哈希桶,再赶上一起触发扩容,这基本就是 HashMap 线程不安全的“重灾区”。数据覆盖、丢失、死循环,全是从这儿冒出来的。
下面这个场景,是 HashMap 线程不安全里最经典、最常见的一个,咱们用流程图加文字完整还原一遍:

除了数据覆盖,HashMap 在多线程下还会搞出另外几个幺蛾子:
比如多线程同时往同一个哈希桶的链表尾部追加节点,大家都在改链表指针,结果就是部分节点根本没挂上去,直接失踪。
JDK1.7 扩容用的是头插法,多线程并发扩容时,链表会变成环形链表。你后面再调用 get() 方法遍历这个环,程序就死循环了,CPU 直接飙到 100%,下都下不来。
到了 JDK1.8,把头插法改成尾插法,扩容死循环的问题算是解决了,但它依然不是线程安全的。
HashMap 里的 size 变量没有原子性保障,多线程同时往里插数据,size 的计数不是少算就是错算,集合的实际大小和你看到的永远对不上。
多个线程一起触发扩容,数组迁移的时候数据就会乱成一锅粥,最后出现重复数据、null 数据、节点丢失,什么情况都有可能。
| 版本 | 插入方式 | 线程不安全问题 | 核心区别 |
|---|---|---|---|
| JDK1.7 | 头插法 | 数据覆盖、死循环、数据丢失 | 扩容会形成环形链表,死循环风险高 |
| JDK1.8 | 尾插法 | 数据覆盖、数据丢失、size错误 | 解决死循环,但依旧无锁,不安全 |
答:
答:优先用 ConcurrentHashMap,它采用分段锁加 CAS 加 synchronized 来保证线程安全,性能远高于 Hashtable。
HashMap 线程不安全这件事,说到底是并发编程里的一个基础题,但也是面试里最容易翻车的地方。把无锁设计导致的并发冲突这个点吃透了,不光面试能轻松过关,实际开发里也能少踩几个因为误用 HashMap 导致的线上故障。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8