本篇内容:1500+字
建议阅读时间:4分钟

我们都知道 HashMap 是线程不安全的,那 HashMap 为什么线程不安全?JDK1.8 还有这些问题吗?如何解决这些问题呢?本文将对该问题进行解密。

多线程下扩容死循环

JDK1.7中的 HashMap 使用头插法插入元素,在多线程的环境下,扩容的时候有可能导致环形链表的出现,形成死循环。因此,JDK1.8使用尾插法插入元素,在扩容时会保持链表元素原本的顺序,不会出现环形链表的问题。

下面看看多线程情况下, JDK1.7 扩容死循环问题的分析。

新建一个更大尺寸的hash表,然后把数据从老的hash表中迁移到新的hash表中。重点看下transfer方法:

假设HashMap初始化大小为2,hash算法就是用key mod 表的长度,在mod 2以后都冲突在table[1]这里了。负载因子是 1.5 (默认为 0.75 ),由公式threshold=负载因子 * hash表长度可得,threshold=1.5 * 2 =3,size=3,而 size>=threshold 就要扩容,所以 hash表要 resize 成 4。

未resize前的table如下图:

正常的ReHash,得到的结果如下图所示:

我们来看看多线程下的ReHash,假设现在有两个线程同时进行,线程1和线程2,两个线程都会新建新的数组,下面是resize 的过程。

Step1:

假设 线程1 在执行到Entry<K,V> next = e.next;之后,cpu 时间片用完了,被调度挂起,这时线程1的 e 指向 节点A,线程1的 next 指向节点B。

线程2继续执行,

Step2:

线程 1 被调度回来执行。

  • 先是执行 newTalbe[i] = e;
  • 然后是e = next,导致了e指向了节点B,
  • 而下一次循环的next = e.next导致了next指向了节点A。

Step3:

线程1 接着工作。把节点B摘下来,放到newTable[i]的第一个,然后把e和next往下移

Step4: 出现环形链表

e.next = newTable[i] 导致A.next 指向了 节点B,此时的B.next 已经指向了节点A,出现环形链表

如果get一个在此链表中不存在的key时,就会出现死循环了。如 get(11)时,就发生了死循环。

分析见get方法的源码:

for循环中的e = e.next永远不会为空,那么,如果get一个在这个链表中不存在的key时,就会出现死循环了。

多线程的put可能导致元素的丢失

多线程同时执行 put 操作,如果计算出来的索引位置是相同的,那会造成前一个 key 被后一个 key 覆盖,从而导致元素的丢失。此问题在JDK 1.7和 JDK 1.8 中都存在。

我们来看下JDK 1.8 中 put 方法的部分源码,重点看黄色部分:

我们来演示个例子。

假设线程1和线程2同时执行put,线程1执行put(“1”, “A”),线程2执行put(“5”, “B”),hash算法就是用key mod 表的长度,表长度为4,在mod 4 以后都冲突在table[1]这里了。注:下面的例子,只演示了 #1#2代码的情况,其他代码也会出现类似情况。

正常情况下,put完成后,table的状态应该是下图中的任意一个。

下面来看看异常情况,两个线程都执行了#1处的if ((p = tab[i = (n - 1) & hash]) == null)这句代码。

此时假设线程1 先执行#2处的tab[i] = newNode(hash, key, value, null);

那么table会变成如下状态:

紧接着线程2 执行tab[i] = newNode(hash, key, value, null);

此时table会变成如下状态:

这样一来,元素A就丢失了。

put和get并发时,可能导致get为null

线程1执行put时,因为元素个数超出threshold而导致rehash,线程2此时执行get,有可能导致这个问题。此问题在JDK 1.7和 JDK 1.8 中都存在。

我们来看下JDK 1.8 中 resize 方法的部分源码,重点看黄色部分:

在代码#1位置,用新计算的容量new了一个新的hash表,#2将新创建的空hash表赋值给实例变量table。

注意此时实例变量table是空的,如果此时另一个线程执行get,就会get出null。

最后

如何解决这些问题呢?多线程情况下应该使用什么呢?

当然是官方推荐的 ConcurrentHashMap。关于 ConcurrentHashMap是如何保证线程安全的?JDK1.7 和 1.8 在实现上又有什么区别?具体分析将在下篇放出,敬请期待哦!

巨人的肩膀

http://mailinator.blogspot.com/2009/06/beautiful-race-condition.html

https://coolshell.cn/articles/9606.html

https://juejin.cn/post/6844903554264596487

https://juejin.cn/post/6844903796225605640

hashmap为什么线程不安全_为什么HashMap线程不安全?相关推荐

  1. hashmap为什么用红黑树_关于HashMap的实现,一篇文章带你彻底搞懂,再也不用担心被欺负

    推荐学习 刷透近200道数据结构与算法,成功加冕"题王",挤进梦中的字节 面试官杠上Spring是种什么体验?莫慌,送你一套面试/大纲/源码 前言 在介绍HashMap之前先了解一 ...

  2. python是如何实现进程池和线程池的_进程、线程、线程池和协程如何理解?

    1.进程.线程.线程池的概念 进程是一个动态的过程,是一个活动的实体.简单来说,一个应用程序的运行就可以被看做是一个进程,而线程,是运行中的实际的任务执行者.可以说,进程中包含了多个可以同时运行的线程 ...

  3. java 线程 获取消息_获取java线程中信息

    怎样获取java线程中信息? 在进行多线程编程中,比较重要也是比较困难的一个操作就是如何获取线程中的信息.大多数人会采取比较常见的一种方法就是将线程中要返回的结果存储在一个字段中,然后再提供一个获取方 ...

  4. burp爆破线程设置多少_你知道线程池创建多少线程比较合理吗?

    为什么会使用多线程 创建多少线程比较合适 结束语 <Java 2019 超神之路> <Dubbo 实现原理与源码解析 -- 精品合集> <Spring 实现原理与源码解析 ...

  5. java线程切换速度_为什么说线程太多,cpu切换线程会浪费很多时间?

    cpu在执行代码的时候[以下说明只在linux平台上,win我不会] 该程序已经是ELF executable file 且该文件内部按ELF格式存储了机器指令+数据 同时该文件必须引用linux 的 ...

  6. java线程卡住排查_基于 Java 线程栈 排查问题

    除日志外,还有没有别的方式跟踪线上服务问题呢?或者,跟踪并排除日志里无法发现的问题? 方法当然是有的,就是通过现场快照定位并发现问题.我们所说的现场,主要指这两方面: Java 线程栈.线程栈是Jav ...

  7. java定时器阻塞主线程_Java基础_死锁、线程组、定时器Timer

    一.死锁问题: 死锁是这样一种情形:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放.由于线程被无限期地阻塞,因此程序不可能正常终止. 比如,线程一需要第一把所,此时锁处于空闲状态,给了 ...

  8. java 线程亲缘性_亲缘性线程池,这是什么鬼?

    一.前言 JDK中的线程池主要解决两个问题: 一方面当执行大量异步任务时候线程池能够提供较好的性能,在不使用线程池的时,每当需要执行异步任务时候是直接 new一线程运行,而线程的创建和销毁是需要开销的 ...

  9. hashmap为什么线程不安全_什么时候线程不安全?怎样做到线程安全?怎么扩展线程安全的类?...

    本文同名博客老炮说Java:https://www.laopaojava.com/,每天更新Spring/SpringMvc/SpringBoot/实战项目等文章资料 顺便再给大家推荐一套Spring ...

最新文章

  1. Kubernetes 第五章 YAML
  2. DBGrid 应用全书(一)
  3. python里graphics的使用_使用graphics.py实现2048小游戏
  4. oracle 存储 更新,oracle 更新空间数据存储过程语句
  5. puppet系列之nginx+php日志切割与salt结合使用
  6. C#刷剑指Offer | 从上到下打印二叉树
  7. 做对三件事,你也能像聪明人一样高速成长!【文末有福利】
  8. Java/JSP中使用JDBC连接SQL Server 2005~(2008类似)
  9. excel取整数的函数_Excel教程:取整函数INT 与TRUNC~~Excel新技能
  10. 初学者的SDN学习之路
  11. Jenkins 之插件 Publish JUint test result reports 的使用
  12. 电子名片怎么制作,制作一张电子名片难吗?
  13. 【Arcgis操作】模块化(批量、自动化)计算多个图层的面积
  14. PHP获取当前域名的记录
  15. Window 打开新窗口的几种方式 window.location.href、window.open、window.showModalDialog
  16. 生活污水处理设备让污水无处躲藏
  17. MP40N120-ASEMI场效应管MP40N120
  18. 这几款浏览器插件提升科研效率,2023年你安装上了吗
  19. 如何以正确地姿势AK SQL查询50题(精华篇)
  20. 基于 SpringBoot 的个人博客系统设计与实现(含论文与程序代码).rar

热门文章

  1. python包含html5么_python-HTML(HTML5级别)
  2. java生产消费线程小例子
  3. 催护 ---- 题都城南庄
  4. loginservlet.java_求助HTTP Status 404 - /Book/servlet/cn.servlet.LoginServlet
  5. oracle ocr掉盘,恢复OCR磁盘组一则
  6. 基于swing的java系统_Java实验--基于Swing的简单的歌曲信息管理系统(一)
  7. 打印冻结窗格怎么保证每页都有_在打印Excel表时怎么设置才能实现每页都打印标题行...
  8. 程序包sun.misc不存在
  9. linux下源码安装log4cxx
  10. Android开发笔记(一百四十)Word文件的读取与显示