回答
HashMap 是线程不安全的,主要体现在如下几个方面(Java 8)。
数据覆盖
在调用 put()
方法时,如果两个线程同时调用 put()
方法,且两个线程在同一个桶位置插入数据,这个一个线程的数据可能会被另一个线程给覆盖掉。
- 线程 A 执行完代码 ① ,然后挂起。
- 线程 B 执行完 ①、② 后,继续执行下面代码。
- 线程 A 继续执行 ②,这个时候线程 A 的数据会覆盖掉线程 B 的数据
数据丢失
数据丢失可能会体现在多个地方,这里大明哥就介绍两个地方。
一、扩容过程中的元素迁移导致的数据丢失
在迁移元素到新的数组中时,原始数组中的链表会重新打散,拆分为两个链表,低位链表保留在原始索引位置,高位链表要迁移到新的索引位置。这个过程要遍历旧链表中的每一个节点进行重分配。如果此时有其他线程尝试插入新元素,而resize()
操作尚未完成,可能会导致在resize()
过程中新插入的数据被遗漏。
put()
部分源码
resize()
部分源码
比如线程 A 在执行 resize()
方法,线程 B 执行 putVal()
方法。
- 线程 A 在处理最后一个节点,此时
next = e.next() == null
,即代码 ① 处,线程 A 挂起。 - 线程 B 执行执行到 ① 处的时候发现
(e = p.next) = null,
所以将新节点插入其中。 - 但是,对于线程 A 而言,next 已经为 null,它已经不会再处理这个链表了,所以会导致线程 B 新插入的结点互遗漏掉。
二、链表重组的线程安全问题
在resize()
方法中,为了保持元素的顺序,会对旧链表中的元素进行重新链接,形成两个链表,然后分别链接到新的桶数组中。如果在这个过程中,其他线程对这些链表进行修改(如插入、删除操作),可能会导致链表结构被破坏,从而导致元素丢失。
最后
其实 HashMap 中存在很多线程安全的问题,还有类似下面这段代码:
这两个参数定义如下:
transient int modCount;
transient int size;
modCount
、size
连最基本的可见性都无法保证,何来的线程安全?
有一点要注意,Java 8 中并不会出现 Java 7 中那样的死循环链。但网上很多文章都说 Java 8 在多线程环境下不会有数据丢失问题,这是错误的,大明哥用一段代码来验证下:
public class HashMapTest {
public static void main(String[] args) throws InterruptedException {
Map<Integer,Integer> hashMap = new HashMap<>();
ExecutorService service = Executors.newFixedThreadPool(100);
for (int i = 0; i < 100; i++) {
int threadNum = i;
service.execute(() -> {
for (int j = 0; j < 1000; j++) {
hashMap.put(threadNum * 1000 + j, j);
}
});
}
service.shutdown();
service.awaitTermination(1, TimeUnit.HOURS);
System.out.println("hashMap.size() = " + hashMap.size());
}
}
启动 100 个线程,每个线程向 HashMap 插入 1000 个数据,如果数据不会丢失,那么最终的结果就是 100*1000
,那事实呢?
HashMap 为了保证高性能,原本就不是为了线程安全而设计的,如果要使用线程安全的 HashMap ,我们可以使用如下两个:
Collections.synchronizedMap(Map)
:通过该方法可以创建一个所有方法都是同步的Map
,由于每次访问都需要进行同步,所以它在并发环境下性能较低。ConcurrentHashMap
:一个高性能的线程安全的 HashMap,推荐使用。
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。