2024-03-14  阅读(59)
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://www.skjava.com/mianshi/baodian/detail/1897293049

回答

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;

modCountsize 连最基本的可见性都无法保证,何来的线程安全?

有一点要注意,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,推荐使用。
阅读全文
  • 点赞