首页 > 其他分享 >HashMap为什么存在线程不安全呢?

HashMap为什么存在线程不安全呢?

时间:2023-04-28 19:32:29浏览次数:43  
标签:插法 结点 HashMap next 链表 安全 线程 死循环


关注Java后端技术栈

回复“面试”获取最新资料

本文主要探讨下HashMap 在多线程环境下容易出现哪些问题,深层次理解其中的HashMap

我们都知道HashMap是线程不安全的,但是HashMap在咱们日常工作中使用频率在所有map中确实属于比较高的。因为它可以满足我们大多数的场景了。

HashMap为什么存在线程不安全呢?_结点

上面展示了java中Map的继承图,Map是一个接口,我们常用的实现类有

HashMap

LinkedHashMap

TreeMap

HashTable

数据覆盖问题

两个线程执行put()操作时,可能导致数据覆盖。JDK1.7版本和JDK1.8版本的都存在此问题,这里以 JDK1.7为例。

假设 A、B 两个线程同时执行put()操作,且两个 key 都指向同一个 buekct,那么此时两个结点,都会做头插法。先看这里的代码实现:

public V put(K key, V value) {
    //...
    addEntry(hash, key, value, i);
}


void addEntry(int hash, K key, V value, int bucketIndex) {
    //...
    createEntry(hash, key, value, bucketIndex);
}

void createEntry(int hash, K key, V value, int bucketIndex) {
    Entry<K,V> e = table[bucketIndex];
    table[bucketIndex] = new Entry<>(hash, key, value, e);
    size++;
}

看下最后的createEntry()方法,首先获取到了 bucket 上的头结点,然后再将新结点作为 bucket 的头部,并指向旧的头结点,完成一次头插法的操作。当线程 A 和线程 B 都获取到了 bucket 的头结点后,若此时线程 A 的时间片用完,线程 B 将其新数据完成了头插法操作,此时轮到线程 A 操作,但这时线程 A 所据有的旧头结点已经过时了(并未包含线程 B 刚插入的新结点),线程 A 再做头插法操作,就会抹掉 B 刚刚新增的结点,导致数据丢失。

其实不光是put()操作,删除操作、修改操作,同样都会有覆盖问题。

扩容时导致死循环

这是最常遇到的情况,也是面试经常被问及的考题。但说实话,这个多线程环境下导致的死循环问题,并不是那么容易解释清楚,因为这里已经深入到了扩容的细节。这里尽可能简单的描述死循环的产生过程。

另外,只有 JDK1.7 及以前的版本会存在死循环现象,在JDK1.8 中,resize()方式已经做了调整,使用两队链表,且都是使用的尾插法,及时多线程下,也顶多是从头结点再做一次尾插法,不会造成死循环。而JDK1.7能造成死循环,就是因为 resize()时使用了头插法,将原本的顺序做了反转,才留下了死循环的机会。

在进一步说明死循环的过程前,我们先看下JDK1.7中的扩容代码片段:

void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length;
    for (Entry<K,V> e : table) {
        while(null != e) {
            Entry<K,V> next = e.next;
            if (rehash) {
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            int i = indexFor(e.hash, newCapacity);
            e.next = newTable[i];
            newTable[i] = e;
            e = next;
        }
    }
}

这段代码是HashMap的扩容操作,重新定位每个桶的下标,并采用头插法将元素迁移到新数组中。头插法会将链表的顺序翻转,这也是形成死循环的关键点。

其实就是简单的链表反转,再进一步简化的话,分为当前结点e,以及下一个结点e.next。我们以链表a->b->c->null为例,两个线程 A 和 B,分别做扩容操作。

原表: 

HashMap为什么存在线程不安全呢?_结点_02

 线程 A 和 B 各自新增了一个新的哈希 table,在线程 A 已做完扩容操作后,线程 B 才开始扩容。此时对于线程 B 来说,当前结点e指向 a 结点,下一个结点e.next仍然指向 b 结点(此时在线程 A 的链表中,已经是c->b->a的顺序)。按照头插法,哈希表的 bucket 指向 a 结点,此时 a 结点成为线程 B 中链表的头结点,如下图所示: 

HashMap为什么存在线程不安全呢?_结点_03

 a 结点成为线程 B 中链表的头结点后,下一个结点e.next为 b 结点。既然下一个结点e.next不为 null,那么当前结点e就变成了 b 结点,下一个结点e.next变为 a 结点。继续执行头插法,将 b 变为链表的头结点,同时 next 指针指向旧的头节点 a,如下图: 

HashMap为什么存在线程不安全呢?_结点_04

 此时,下一个结点e.next为 a 节点,不为 null,继续头插法。指针后移,那么当前结点e就成为了 a 结点,下一个结点为 null。将 a 结点作为线程 B 链表中的头结点,并将 next 指针指向原来的旧头结点 b,如下图所示: 

HashMap为什么存在线程不安全呢?_头插法_05

 此时,已形成环链表。同时下一个结点e.next为 null,流程结束。

总结

如果想在多线程环境下使用 HashMap,很容易引起各类问题,上面仅为不安全问题的两个典型示例,具体问题无法一一列举,但大体会分为以下三类:

死循环

数据重复

数据丢失(覆盖)

注意:在JDK1.5之前,多线程环境往往使用 HashTable,但在JDK1.5及以后的版本中,在并发包中引入了专门用于多线程环境的ConcurrentHashMap类,采用分段锁实现了线程安全,相比 HashTable 有更高的性能,推荐使用。

标签:插法,结点,HashMap,next,链表,安全,线程,死循环
From: https://blog.51cto.com/u_11702014/6235417

相关文章

  • 防灾减灾救灾,生命通道保障安全
    在自然灾害频发的今天,如何保障人们的生命安全成为了一个重要问题。而建立生命通道则是一种有效的方式。生命通道是指在紧急情况下,为了保障人们尽快逃离危险区域而设置的一条安全通道。因此,在防灾减灾救灾过程中,确保安全通道的存在和可用性至关重要。第一,什么是生命通道?生命通道是指......
  • 如何避免跨越红线?——安全管理的重要性
    在现代社会中,安全管理已经成为了一个非常重要的话题。尤其是在一些危险行业中,如建筑、化工等领域,安全管理更是至关重要。然而,很多人并不知道如何有效地避免跨越红线,从而导致了许多不必要的安全事故。因此,如何避免跨越红线成为了每个人都应该关注的问题。首先,要明确什么是“红线”。......
  • 谈剑峰:把信息安全核心技术掌握在中国人自己手中
    http://www.rmzxb.com.cn/c/2017-06-01/1567122.shtml?n2m=1 谈剑锋,高级工程师[88],无党派人士[87]。第十三届全国政协委员[10]、第十三届上海市政协常委[30][34],第五空间信息科技研究院院长[15],上海市信息安全行业协会名誉会长[10],是信息安全及数据安全领域资深......
  • STM32:RTthread_线程
    1微处理器系统    随着产品功能的增多,裸机系统不能够满足产品需求,引入RTOS实时操作系统的多线程管理,可以增加程序的稳定性逻辑性,便于管理;2线程  通常默认一个能独立实现功能的函数,称之为线程;多线程管理的意思就是这个程序可以实现多个功能管理;  2.1线程栈   ......
  • 医院故障报修系统,为医疗安全保驾护航
    医院故障报修系统为提高医院后勤管理水平、提升服务能力,切实保障医院医教研工作顺利开展,不断改善医护人员工作环境,提升患者就诊体验,医院后勤运维中心持续推进后勤运维管理平台建设,为医疗安全保驾护航!医院故障报修系统针对医院出现的这种问题,结合报修人、维修师傅(技术员)、管理员的......
  • web安全基础-渗透相关字段
    1、Set-Cookie和cookies2、csp3、x-frame-option4、x-xss-frame5、location6、referer和origin7、user-agent、xff作为sql注入参数点、收集用户信息8、server、x-powered-for收集服务端信息9、cors......
  • Java多线程之---用 CountDownLatch 说明 AQS 的实现原理
    本文基于jdk1.8。CountDownLatch的使用前面的文章中说到了volatile以及用volatile来实现自旋锁,例如java.util.concurrent.atomic包下的工具类。但是volatile的使用场景毕竟有限,很多的情况下并不是适用,这个时候就需要synchronized或者各种锁实现了。今天就来说一下几......
  • Java 中的几种线程池,你之前用对了吗
    好久不发文章了,难道是因为忙,其实是因为懒。这是一篇关于线程池使用和基本原理的科普水文,如果你经常用到线程池,不知道你的用法标准不标准,是否有隐藏的OOM风险。不经常用线程池的同学,还有对几种线程的使用不甚了解的同学可以读一下此文。为什么要使用线程池虽然大家应该都已经很清......
  • 信息安全之应用层协议
      应用层 TCP/IP模型的下三层,分别是网络接入层、网络层和传输层。它们都是为应用层服务的,传输应用层的各种数据,现在我们就来看看最高层的​应用层。应用层在TCP/IP模型中,应用层提供的服务相当于OSI模型的应用层、表示层和会话层的服务总和。不仅包含了​管......
  • C# 多线程
    首先要关注电脑配置是否是多核多CPU的。因为一个CPU在同一时刻只能运行一个线程,但是多个CPU在同一时刻就可以运行多个线程。 多线程的优点:1、可以同时完成多个任务;2、可以使程序的响应速度更快;3、可以让占用大量处理时间的任务或当前没有进行处理的任务定期将处理时间让给......