【Java】HashMap线程安全吗?
这篇是关于HashMap是否是线程安全的讲解,希望对大家有所帮助,也请大佬们多多指教,欢迎点赞、评论!!
HashMap不是线程安全的。
它的设计初衷是为了单线程环境下的高效哈希表操作,内部没有任何同步机制。
(如synchronized锁或volatile关键字),在多线程并发修改时会导致数据不一致、死循环等问题。
一、典型不安全场景:多线程并发扩容导致链表成环(JDK1.7及以前)
在JDK1.7中,HashMap的底层结构是 数组+链表,当元素数量超过阈值(capacity*loadFactor)时会触发扩容(resize)
核心步骤是将就数组的元素迁移到新数组中(transfer方法)
多线程并发扩容时,可能导致链表节点的引用关系被破坏,形成环形链表,后续执行get操作时会陷入无限循环,导致CPU飙升。
场景复现步骤:
1、初始状态:
假设HashMap容量为2,负载因子为0.75,阈值为1.已有一个元素key=0(哈希后 落在索引为0),此时再插入元素会触发扩容(容量变为4)。
2.并发扩容:
- 线程A和线程B同时执行
put
方法,均触发扩容,进入transfer
方法。 - 线程A先执行部分迁移,将旧数组索引0的链表(假设只有key=0)迁移到新数组,此时链表节点的next引用暂时被修改。
- 线程B暂停后恢复执行,继续迁移时复用了线程A修改后的next引用,导致链表节点之间形成循环(比如:节点A的next引用指向节点B,节点B的next引用指向节点A)。
3.后果:
当后续线程执行get
操作查询环形链表中的元素时,会陷入无限循环,无法退出。
二、其他线程不安全的场景:
1.数据覆盖:
多个线程同时执行put
操作时,若两个线程计算出的哈希索引相同时,且同时判断该位置为空,会导致后执行的线程覆盖先执行线程插入的数据,造成数据丢失。
// 示例代码(多线程执行)
HashMap<String, Integer> map = new HashMap<>();// 线程1
new Thread(() -> map.put("key", 1)).start();
// 线程2
new Thread(() -> map.put("key", 2)).start();// 最终map中"key"的值可能是1或2,也可能出现异常(取决于执行时序)
2.size计数不准:
HashMap的size
字段用于记录元素数量,多线程并发put/remove
时,size++/size--
操作不是原子性的(可能被打断),导致最终size与实际元素数量不符。
三、总结:
1.HashMap的线程不安全本质是缺乏同步机制,无法保证多线程操作的原子性、可见性和有序性,导致内部数据结构(数组、链表/红黑树)被破坏或数据不一致。
2.若需在多线程环境中使用哈希表,应该选择线程安全的替代类,如ConcurrentHashMap(
JDK1.5+,高效并发)或、Collection.synchronizedMap(new HashMap<>())
(全表加锁,效率较低)。