深入探究Java中的HashMap为什么会产生死循环

 更新时间:2023年05月26日 14:10:51   作者:javacn  
HashMap 死循环发生在 JDK 1.8 之前的版本中,这篇文章主要来和大家深入探究一下为什么Java中HashMap会产生死循环,感兴趣的小伙伴可以了解一下

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

死循环原因

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

  • HashMap 使用头插法进行数据插入(JDK 1.8 之前);
  • 多线程同时添加;
  • 触发了 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 节点,如下图所示:

死循环执行步骤2

死循环的第二步操作是,线程 T2 时间片用完进入休眠状态,而线程 T1 开始执行扩容操作,一直到线程 T1 扩容完成后,线程 T2 才被唤醒,扩容之后的场景如下图所示:

从上图可知线程 T1 执行之后,因为是头插法,所以 HashMap 的顺序已经发生了改变,但线程 T2 对于发生的一切是不可知的,所以它的指向元素依然没变,如上图展示的那样,T2 指向的是 A 元素,T2.next 指向的节点是 B 元素。

死循环执行步骤3

当线程 T1 执行完,而线程 T2 恢复执行时,死循环就建立了,如下图所示:

因为 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中的HashMap为什么会产生死循环的文章就介绍到这了,更多相关Java HashMap死循环内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java详解对象终止方法finalize()的用法

    Java详解对象终止方法finalize()的用法

    在前面的 jvm 中, 需要补充几个部分的内容, 接着来看 finalize() 机制, 它可以使接近死亡的对象复活, 下来我们来看是怎么一回事
    2022-05-05
  • Spring Boot实现文件上传下载

    Spring Boot实现文件上传下载

    这篇文章主要为大家详细介绍了Spring Boot实现文件上传下载,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-08-08
  • Maven的安装配置详解

    Maven的安装配置详解

    这篇文章主要介绍了Maven的安装配置详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-08-08
  • SpringBoot进行模块化开发的5种组织方式小结

    SpringBoot进行模块化开发的5种组织方式小结

    模块化开发作为解决复杂性的关键策略,能够有效提升代码的可维护性、可扩展性和团队协作效率,本文将介绍SpringBoot模块化开发的5种组织方式,需要的小伙伴可以参考一下
    2025-06-06
  • 浅析Java中StringBuffer和StringBuilder的使用

    浅析Java中StringBuffer和StringBuilder的使用

    当对字符串进行修改的时候,需要使用 StringBuffer 和 StringBuilder 类。本文就来和大家简单聊聊这二者的使用与区别吧,希望对大家有所帮助
    2023-04-04
  • SpringBoot 配置提示功能(超详细)

    SpringBoot 配置提示功能(超详细)

    这篇文章主要介绍了SpringBoot 配置提示功能,本文给大家介绍的超详细,通过实例代码给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2019-10-10
  • 学习java编程后可以走哪些职业道路

    学习java编程后可以走哪些职业道路

    在本篇文章里给大家介绍了关于学习java后的职业道路,以及需要学习的相关知识内容,有兴趣的朋友们可以跟着学习下。
    2022-11-11
  • 详解spring security之httpSecurity使用示例

    详解spring security之httpSecurity使用示例

    这篇文章主要介绍了详解spring security之httpSecurity使用示例,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-08-08
  • Java设计模式之适配器模式简介

    Java设计模式之适配器模式简介

    这篇文章主要介绍了Java设计模式之适配器模式,需要的朋友可以参考下
    2014-07-07
  • JAVA设计模式之组合模式原理与用法详解

    JAVA设计模式之组合模式原理与用法详解

    这篇文章主要介绍了JAVA设计模式之组合模式,简单说明了组合模式的原理,并结合实例分析了java组合模式的具体用法,需要的朋友可以参考下
    2017-08-08

最新评论