首页 > 其他分享 >为什么 HashMap 会死循环?

为什么 HashMap 会死循环?

时间:2023-05-23 09:03:49浏览次数:55  
标签:为什么 HashMap T2 T1 线程 节点 死循环

HashMap 死循环发生在 JDK 1.8 之前的版本中,它是指在并发环境下,因为多个线程同时进行 put 操作,导致链表形成环形数据结构,一旦形成环形数据结构,在 get(key) 的时候就会产生死循环。如下图所示:
image.png

死循环原因

HashMap 导致死循环的原因是由以下条件共同导致的:

  1. HashMap 使用头插法进行数据插入(JDK 1.8 之前);

  2. 多线程同时添加;

  3. 触发了 HashMap 扩容。

什么是头插法?

头插法是指新来的值会取代原有的值,插入到链表的头部,如下图所示。

原链表如下图所示:

此时使用头插入插入一个元素 Z,如下图所示:

头插法会导致 HashMap 在进行扩容时,链表的顺序发生反转,如下图所示:

因为在 HashMap 扩容时,会先从旧 HashMap 的头节点读取并插入到新 HashMap 节点中,旧节点的读取顺序是 A -> B -> C,于是插入到新 HashMap 中的顺序就变成了 C -> B -> A,这样就破坏了链表的顺序,导致了链表反转。

死循环产生过程

死循环执行步骤1

死循环是因为并发 HashMap 扩容导致的,并发扩容的第一步,线程 T1 和线程 T2 要对 HashMap 进行扩容操作,此时 T1 和 T2 指向的是链表的头结点元素 A,而 T1 和 T2 的下一个节点,也就是 T1.next 和 T2.next 指向的是 B 节点,如下图所示:
image.png

死循环执行步骤2

死循环的第二步操作是,线程 T2 时间片用完进入休眠状态,而线程 T1 开始执行扩容操作,一直到线程 T1 扩容完成后,线程 T2 才被唤醒,扩容之后的场景如下图所示:
image.png
从上图可知线程 T1 执行之后,因为是头插法,所以 HashMap 的顺序已经发生了改变,但线程 T2 对于发生的一切是不可知的,所以它的指向元素依然没变,如上图展示的那样,T2 指向的是 A 元素,T2.next 指向的节点是 B 元素。

死循环执行步骤3

当线程 T1 执行完,而线程 T2 恢复执行时,死循环就建立了,如下图所示:
image.png
因为 T1 执行完扩容之后 B 节点的下一个节点是 A,而 T2 线程指向的首节点是 A,第二个节点是 B,这个顺序刚好和 T1 扩完容完之后的节点顺序是相反的。T1 执行完之后的顺序是 B 到 A,而 T2 的顺序是 A 到 B,这样 A 节点和 B 节点就形成死循环了,这就是 HashMap 死循环导致的原因。

解决方案

HashMap 死循环的常用解决方案有以下几个:

  1. 升级到高版本 JDK(JDK 1.8 以上),高版本 JDK 使用的是尾插法插入新元素的,所以不会产生死循环的问题;

  2. 使用线程安全容器 ConcurrentHashMap 替代(推荐使用此方案);

  3. 使用线程安全容器 Hashtable 替代(性能低,不建议使用);

  4. 使用 synchronized 或 Lock 加锁 HashMap 之后,再进行操作,相当于多线程排队执行(比较麻烦,也不建议使用)。

小结

HashMap 死循环发生在 JDK 1.7 版本中,形成死循环的原因是 HashMap 在 JDK 1.7 使用的是头插法,头插法 + 多线程并发操作 + HashMap 扩容,这几个点加在一起就形成了 HashMap 的死循环,解决死循环可以采用线程安全容器 ConcurrentHashMap 替代。

本文已收录至《Java面试突击》,专注 Java 面试 100 年,查看更多:www.javacn.site

标签:为什么,HashMap,T2,T1,线程,节点,死循环
From: https://www.cnblogs.com/javacn123/p/17422250.html

相关文章

  • 为什么不能向下兼容呢?这是因为不同版本的 Refs 文件系统之间可能存在较大的差异,如接口
    Refs文件系统是在WindowsServer2012R2引入的,目前主要用于Windows服务器操作系统中。截至目前为止,Windows服务器操作系统中已经支持了三个版本的Refs文件系统:RefsV1:WindowsServer2012R2中引入的第一代Refs版本,该版本引入了Refs文件系统,并支持自动修复、数据......
  • 为什么古老的华夏文明在近现代会落后于欧洲文明?
    linkASML(生产光刻机卡我们脖子那个公司)有一个算法部门,总部在荷兰,我曾经在那研究路径优化问题,当时用到过基因算法,这种算法的原理很简单,就是模仿基因的遗传、突变和交互不断迭代去寻找近似最优解。但也不是完全没有难度,其中的一个难度就在于需要调整突变颗粒度,颗粒度太大会经常错过最......
  • 为什么 GPU 能够极大地提高仿真速度?
    这里的提速主要是针对时域电磁算法的。因为时域算法的蛙跳推进模式仅对大量存放在固定位置的数据进行完全相同的且是简单的操作(移位相加),这正是GPU这类众核SIMD架构所进行的运算,即ALU与内存的存取速度(又称带宽)直接决定了整个运算速度。 下表给出了GPU与高速CPU数据总......
  • ConcurrentHashMap 相关
    为什么ConcurrentHashMap要放弃分段锁?答:1、因为在JDK7中 Segment 继承了重入锁ReentrantLock。在每个 Segment 在增长的时候,这时候锁的粒度也会在不断的增长。每个锁控制的是一段,当分段很多,并且加锁的分段不连续的时候,内存空间的浪费比较严重。在并发操作中,因为分段锁的......
  • Oracle为什么不需要double write?
    近期看到朋友圈转发了几篇关于MySQLinnodbdoublewrite的文章;感觉都还不错。突然想到为什么Oracle没有这个东西?PostgreSQL是否也有类似机制?在网上搜了一下,发现有人之前简单写过类似文章。。。。但是;毫无疑问,没有一篇能够完全分析透彻的的。 所以,我想来好好说一下这个问题。 首......
  • 为什么MySQL单表不能超过2000万行?
    摘要:MySQL一张表最多能存多少数据?本文分享自华为云社区《为什么MySQL单表不能超过2000万行?》,作者:GaussDB数据库。最近看到一篇《我说MySQL每张表最好不要超过2000万数据,面试官让我回去等通知》的文章,非常有趣。文中提到,他朋友在面试的过程中说,自己的工作就是把用户操作信息......
  • 为什么袁隆平的英语这么好?这才是学到老的典范!
    文/冰雪(微信公众号:王不留)2021年5月22日13时07分,“共和国勋章”获得者、中国工程院院士、国家杂交水稻工程技术研究中心主任、湖南省政协原副主席袁隆平,因病逝世,享年91岁。一晃两年过去了。袁隆平院士用一粒种子改变了世界,他的成就来自对目标的坚定追求和对新知识的不断获知。袁老......
  • 为什么你很努力,进步却很慢?
    阅读本文大概需要3分钟。经常有人问我类似这样的问题:张哥,我想学习编程,但是基础较差,不知道能不能学成?张哥,我目前做开发有一段时间了,但是总感觉自己进步较慢,不知道如何提升?张哥,我工作有5、6年了,现在有点迷茫,不知道自己的职业规划该怎么选择。我相信以上绝不是个例,有时候,并不是你......
  • Python并发编程:为什么传入进程池的目标函数不执行,也没有报错?
    转载:Python并发编程:为什么传入进程池的目标函数不执行,也没有报错?-知乎(zhihu.com)python初学者使用进程池时,很容易掉坑里! python并发编程中,这个问题是新手经常容易犯的错,十个人,大概有九个都会掉入其中。借此机会,对该问题的前因后果做个记录,分享于此!一、错误代码复现我......
  • 为什么只有Python可以爬虫,C++可以吗?
    Python(英国发音:/ˈpaɪθən/;美国发音:/ˈpaɪθɑːn/),是一种广泛使用的解释型、面向对象、动态数据类型的高级程序设计语言。Python支持多种编程范型,包括结构化、过程式、反射式、面向对象和函数式编程。它拥有动态类型系统和垃圾回收功能,能够自动管理内存使用,并且其本身拥有一个......