为什么说HashMap线程不安全

 更新时间:2023年04月27日 08:58:49   作者:Cosolar  
本文主要介绍了为什么说HashMap线程不安全,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

在Java中,HashMap是一种常用的数据结构,它以键值对的形式存储和管理数据。然而,由于HashMap在多线程环境下存在线程安全问题,因此在使用时需要格外小心。

简单来说:在 hashMap1.7 中扩容的时候,因为采用的是头插法,所以会可能会有循环链表产生,导致数据有问题,在 1.8 版本已修复,改为了尾插法; 在任意版本的 hashMap 中,如果在插入数据时多个线程命中了同一个槽,可能会有数据覆盖的情况发生,导致线程不安全。

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

1. 并发修改导致数据不一致

HashMap的数据结构是基于数组和链表实现的。在进行插入或删除操作时,如果不同线程同时修改同一个位置的元素,就会导致数据不一致的情况。具体来说,当两个线程同时进行插入操作时,假设它们都要插入到同一个数组位置,并且该位置没有元素,那么它们都会认为该位置可以插入元素,最终就会导致其中一个线程的元素被覆盖掉。此外,在进行删除操作时,如果两个线程同时删除同一个元素,也会导致数据不一致的情况。

以下是一个示例代码,展现了两个线程对HashMap进行并发修改的情况:

import java.util.HashMap;
public class HashMapThreadUnsafeExample {
    public static void main(String[] args) throws InterruptedException {
        final HashMap<String, Integer> map = new HashMap<>();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                map.put("key" + i, i);
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                map.put("key" + i, i * 2);
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("map size: " + map.size());
    }
}

上述示例代码中,t1线程和t2线程都向HashMap中插入数据,由于它们在进行插入操作时修改的是同一个位置的元素,因此最终导致了部分数据不一致的情况。例如,当t1线程插入了(key1, 1)以后,t2线程又插入了(key1, 2),这就导致了(key1, 1)被覆盖掉,最终HashMap的大小只有10000而不是20000。

2. 并发扩容导致死循环或数据丢失

当HashMap的元素数量达到一定阈值时,它会触发扩容操作,即重新分配更大的数组并将原来的元素重新映射到新的数组上。然而,在进行扩容操作时,如果不加锁或者加锁不正确,就可能导致死循环或者数据丢失的情况。具体来说,当两个线程同时进行扩容操作时,它们可能会同时将某个元素映射到新的数组上,从而导致该元素被覆盖掉。此外,在进行扩容操作时,如果线程不安全地修改了next指针,就可能会导致死循环的情况。

以下是一个示例代码,展现了两个线程对HashMap进行并发扩容的情况:

import java.util.HashMap;
public class HashMapThreadUnsafeExample {
    public static void main(String[] args) throws InterruptedException {
        final HashMap<String, Integer> map = new HashMap<>(2, 0.75f);
        map.put("key1", 1);
        map.put("key2", 2);
        map.put("key3", 3);
        Thread t1 = new Thread(() -> {
            for (int i = 4; i < 10000; i++) {
                map.put("key" + i, i);
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 4; i < 10000; i++) {
                map.put("key" + i, i * 2);
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("map size: " + map.size());
    }
}

上述示例代码中,t1线程和t2线程都向HashMap中插入数据,并且HashMap被初始化为大小为2,负载因子为0.75,这就意味着HashMap在元素数量达到3时就会进行扩容操作。由于t1和t2线程同时进行扩容操作,它们有可能都将某个元素映射到新的数组上,导致该元素被覆盖掉。此外,在进行扩容操作时,如果线程不安全地修改了next指针,就可能会导致死循环的情况。

除了并发修改和并发扩容外,还有以下情况可能导致HashMap不安全:

3. 非线程安全的迭代器

当使用非线程安全的迭代器遍历HashMap时,如果在遍历的过程中其他线程修改了HashMap的结构,就可能抛出ConcurrentModificationException异常。

以下是一个示例代码,展现了如何通过多线程遍历HashMap以及导致线程不安全的情况:

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class HashMapThreadUnsafeExample {
    public static void main(String[] args) throws InterruptedException {
        final Map<String, Integer> map = new HashMap<>();
        for (int i = 0; i < 10000; i++) {
            map.put("key" + i, i);
        }
        Thread t1 = new Thread(() -> {
            Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator();
            while (iterator.hasNext()) {
                System.out.println(iterator.next().getValue());
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 10000; i < 20000; i++) {
                map.put("key" + i, i);
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
    }
}

上述示例代码中,t1线程遍历了HashMap中的元素,但并没有对其进行加锁保护。同时,在t1线程遍历的过程中,t2线程又进行了另外一部分元素的插入操作,这就导致了HashMap结构的不稳定性,最终可能会抛出ConcurrentModificationException异常。

4. 非线程安全的比较器

当使用非线程安全的比较器来定义HashMap的排序规则时,就可能导致在并发环境下出现数据不一致性的情况。

以下是一个示例代码,展现了如何通过多线程修改HashMap中元素顺序以及导致线程不安全的情况:

import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
public class HashMapThreadUnsafeExample {
    public static void main(String[] args) throws InterruptedException {
        final Map<String, Integer> map = new HashMap<>();
        map.put("key1", 1);
        map.put("key2", 2);
        map.put("key3", 3);
        Comparator<String> comparator = (s1, s2) -> {
            int i1 = Integer.parseInt(s1.substring(3));
            int i2 = Integer.parseInt(s2.substring(3));
            return Integer.compare(i1, i2);
        };
        Thread t1 = new Thread(() -> {
            for (int i = 4; i < 10000; i++) {
                map.put("key" + i, i);
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 4; i < 10000; i++) {
                map.put("key" + i, i * 2);
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("map: " + map);
    }
}

上述示例代码中,HashMap的排序规则使用了一个基于字符串处理的比较器来定义。当t1线程和t2线程同时进行插入操作时,由于它们在不同的元素上执行修改操作,因此并不会出现ConcurrentModificationException异常。然而,由于比较器不是线程安全的,当t1和t2线程同时进行对相同的元素值进行赋值操作时,就可能导致HashMap结构的不稳定性。例如,当t1线程将"key5"的值修改为5时,t2线程可能只修改到"value"字段的一部分,因此最终HashMap中的值可能出现混乱的情况。

写到这里我想告诉大家:HashMap在多线程环境下存在线程安全问题,具体表现为并发修改导致数据不一致和并发扩容导致死循环或数据丢失。因此,在使用HashMap时需要采取相应的线程安全措施,例如使用ConcurrentHashMap、加锁等。

到此这篇关于为什么说HashMap线程不安全的文章就介绍到这了,更多相关HashMap线程不安全内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Maven坐标和依赖的实现示例

    Maven坐标和依赖的实现示例

    本文主要介绍了Maven坐标和依赖的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-04-04
  • Java下3中XML解析 DOM方式、SAX方式和StAX方式

    Java下3中XML解析 DOM方式、SAX方式和StAX方式

    目前我知道的JAVA解析XML的方式有:DOM, SAX, StAX;如果选用这几种,感觉还是有点麻烦;如果使用:JAXB(Java Architecture for XML Binding),个人觉得太方便了
    2013-04-04
  • Arrays.asList方法总结

    Arrays.asList方法总结

    本文主要对Arrays.asList方法进行总结。具有很好的参考价值,下面跟着小编一起来看下吧
    2017-02-02
  • 使用Spring AOP做接口权限校验和日志记录

    使用Spring AOP做接口权限校验和日志记录

    本文介绍了面向切面编程(AOP)的基本概念、应用场景及其在Spring中的实现原理,通过AOP,可以方便地在不修改原有代码的情况下,实现日志记录、权限校验等功能,以学生身份证号查询接口为例,展示了如何定义权限注解、切面类以及权限验证服务,感兴趣的朋友一起看看吧
    2025-01-01
  • 基于Java SSM实现Excel数据批量导入

    基于Java SSM实现Excel数据批量导入

    这篇文章主要为大家详细介绍了基于Java SSM如何实现excel数据批量导入,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-11-11
  • java POI 如何实现Excel单元格内容换行

    java POI 如何实现Excel单元格内容换行

    这篇文章主要介绍了java POI 如何实现Excel单元格内容换行的操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07
  • java 中Collection存储器详解及简单实例

    java 中Collection存储器详解及简单实例

    这篇文章主要介绍了java 中Collection存储器详解及简单实例的相关资料,需要的朋友可以参考下
    2017-04-04
  • Java开发中为什么要使用单例模式详解

    Java开发中为什么要使用单例模式详解

    单例对于大家来说并不陌生,但是在什么时候用单例呢?为什么要用呢?本文就带大家了解一下为什么要使用单例,文中有非常详细的介绍,需要的朋友可以参考下
    2021-06-06
  • 如何通过properties文件配置web.xml中的参数

    如何通过properties文件配置web.xml中的参数

    这篇文章主要介绍了如何通过properties文件配置web.xml中的参数方法,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • ReentrantLock条件变量使多个线程顺序执行

    ReentrantLock条件变量使多个线程顺序执行

    这篇文章主要为大家介绍了ReentrantLock条件变量使多个线程顺序执行,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-12-12

最新评论