HashMap在java并发中如何发生死循环

转载自   HashMap在java并发中如何发生死循环

 

   在多线程环境中,使用HashMap进行put操作时会引起死循环,导致CPU使用接近100%,下面通过代码分析一下为什么会发生死循环。

      首先先分析一下HashMap的数据结构:HashMap底层数据结构是有一个链表数据构成的,HashMap中定义了一个静态内部类作为链表,代码如下(与本文无关的代码省略):

静态内部类entry代码 

  1.     static class Entry<K,V> implements Map.Entry<K,V> {  
  2.         final K key;  
  3.         V value;  
  4.         Entry<K,V> next;  
  5.         final int hash;  
  6.   
  7.         /**  
  8.          * Creates new entry.  
  9.          */  
  10.         Entry(int h, K k, V v, Entry<K,V> n) {  
  11.             value = v;  
  12.             next = n;  
  13.             key = k;  
  14.             hash = h;  
  15.         }  
  16. 、  
  17.     }  

 

Hashmap属性代码 

  1. /**  
  2.  * The table, resized as necessary. Length MUST Always be a power of two.  
  3.  */  
  4. transient Entry[] table;  

    之所以会导致HashMap出现死循环是因为多线程会导致HashMap的Entry节点形成环链,这样当遍历集合时Entry的next节点用于不为空,从而形成死循环

 

    单添加元素时会通过key的hash值确认链表数组下标

  1. public V put(K key, V value) {  
  2.     if (key == null)  
  3.         return putForNullKey(value);  
  4.       
  5.     //确认链表数组位置  
  6.     int hash = hash(key.hashCode());  
  7.     int i = indexFor(hash, table.length);  
  8.       
  9.     //如果key相同则覆盖value部分  
  10.     for (Entry<K,V> e = table[i]; e != null; e = e.next) {  
  11.         Object k;  
  12.         if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {  
  13.             V oldValue = e.value;  
  14.             e.value = value;  
  15.             e.recordAccess(this);  
  16.             return oldValue;  
  17.         }  
  18.     }  
  19.   
  20.     modCount++;  
  21.     //添加链表节点  
  22.     addEntry(hash, key, value, i);  
  23.     return null;  
  24. }  

 

   下面看一下HashMap添加节点的实现

  1. void addEntry(int hash, K key, V value, int bucketIndex) {  
  2.  //bucketIndex 通过key的hash值与链表数组的长度计算得出  
  3.     Entry<K,V> e = table[bucketIndex];  
  4.     //创建链表节点        
  5.   table[bucketIndex] = new Entry<K,V>(hash, key, value, e);  
  6.     
  7.   //判断是否需要扩容  
  8.   if (size++ >= threshold)  
  9.             resize(2 * table.length);  
  10. }  

 

 

    以上部分的实现不会导致链路出现环链,环链一般会出现HashMap扩容是,下面看看扩容的实现:

  1. void resize(int newCapacity) {  
  2.         Entry[] oldTable = table;  
  3.         int oldCapacity = oldTable.length;  
  4.         if (oldCapacity == MAXIMUM_CAPACITY) {  
  5.             threshold = Integer.MAX_VALUE;  
  6.             return;  
  7.         }  
  8.   
  9.         Entry[] newTable = new Entry[newCapacity];  
  10.           
  11.         transfer(newTable);//可能导致环链  
  12.           
  13.         table = newTable;  
  14.         threshold = (int)(newCapacity * loadFactor);  
  15. }  

 

 

  下面transfer的实现

  1. void transfer(Entry[] newTable) {  
  2.     Entry[] src = table;  
  3.     int newCapacity = newTable.length;  
  4.     for (int j = 0; j < src.length; j++) {  
  5.         Entry<K,V> e = src[j];  
  6.         if (e != null) {  
  7.             src[j] = null;  
  8.             do {  
  9.                 Entry<K,V> next = e.next;  
  10.                 int i = indexFor(e.hash, newCapacity);  
  11.                 e.next = newTable[i];  
  12.                 newTable[i] = e;  
  13.                 e = next;  
  14.             } while (e != null);  
  15.         }  
  16.     }  
  17. }  

   这个方法的目的是将原链表数据的数组拷到新的链表数组中,拷贝过程中如果形成环链的呢?下面用一个简单的例子来说明一下:

  1. public class InfiniteLoop {  
  2.   
  3.     static final Map<Integer, Integer> map = new HashMap<Integer, Integer>(20.75f);  
  4.   
  5.     public static void main(String[] args) throws InterruptedException {  
  6.   
  7.         map.put(555);  
  8.   
  9.         new Thread("Thread1") {  
  10.             public void run() {  
  11.                 map.put(777);  
  12.                 System.out.println(map);  
  13.             };  
  14.         }.start();  
  15.   
  16.         new Thread("Thread2") {  
  17.             public void run() {  
  18.                 map.put(333);  
  19.                 System.out.println(map);  
  20.             };  
  21.         }.start();  
  22.   
  23.     }  
  24.   
  25. }  

 

 

   下面通过debug跟踪调试来看看如果导致HashMap形成环链,断点位置:

  1. 线程1的put操作
  2. 线程2的put操作
  3. 线程2的输出操作
  4. HashMap源码transfer方法中的第一行、第六行、第九行

    测试开始

  

  1.  使线程1进入transfer方法第一行,此时map的结构如下

    2.  使线程2进入transfer方法第一行,此时map的结构如下:


 
 3.接着切换回线程1,执行到transfer的第六行,此时map的结构如下:

4.然后切换回线程2使其执行到transfer方法的第六行,此时map的结够如上

5.接着切换回线程1使其执行到transfer方法的第九行,然后切换回线程2使其执行完,此时map的结构如下:


 6.切换回线程1执行循环,因为线程1之前是停在HashMap的transfer方法的第九行处,所以此时transfer方法的节点e的key=3,e.next的key=7

  1. void transfer(Entry[] newTable) {  
  2.         Entry[] src = table;  
  3.         int newCapacity = newTable.length;  
  4.         for (int j = 0; j < src.length; j++) {  
  5.             Entry<K,V> e = src[j];  
  6.             if (e != null) {  
  7.                 src[j] = null;  
  8.                 do {  
  9.                     Entry<K,V> next = e.next;  
  10.                     int i = indexFor(e.hash, newCapacity);//线程1等线程2执行结束后  
  11.                                                           //从此处开始执行  
  12.                                                           //此时e的key=3,e.next.key=7  
  13.                                                           //但是此时的e.next.next的key=3了  
  14.                                                           //(被线程2修改了)  
  15.                     e.next = newTable[i];  
  16.                     newTable[i] = e;  
  17.                     e = next;  
  18.                 } while (e != null);  
  19.             }  
  20.         }  
  21. }  

  

  下面线程1开始执行第一次循环,循环后的map结构如下:


 

接着执行第二次循环:e.key=7,e.next.key=3,e.next.next=null

接着执行第三次循环,从而导致环链形成,map结构如下


 并且此时的map中还丢失了key=5的节点

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/327488.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算机和影视结合专业,计算机专业专业建设总结与典型案例2.5微电影拍摄与后期制作(影视拍摄与后期制作技术)课....

计算机专业专业建设总结与典型案例2.5微电影拍摄与后期制作(影视拍摄与后期制作技术)课. (11页)本资源提供全文预览&#xff0c;点击全文预览即可全文预览,如果喜欢文档就下载吧&#xff0c;查找使用更方便哦&#xff01;9.9 积分微电影拍摄与后期制作(影视拍摄与后期制作技术)…

SpringBoot +Vue前后端分离(笔记)

前后端分离简介 前后端分离 前后端分离就是将⼀个应⽤的前端代码和后端代码分开写&#xff0c;为什么要这样做&#xff1f; 如果不使⽤前后端分离的⽅式&#xff0c;会有哪些问题&#xff1f; 传统的 Java Web 开发中&#xff0c;前端使⽤ JSP 开发&#xff0c;JSP 不是由后…

.NET Core下使用gRpc公开服务(SSL/TLS)

一、前言 前一阵子关于.NET的各大公众号都发表了关于gRpc的消息&#xff0c;而随之而来的就是一波关于.NET Core下如何使用的教程&#xff0c;但是在这众多的教程中基本都是泛泛而谈&#xff0c;难以实际在实际环境中使用&#xff0c;而该篇教程以gRpc为主&#xff0c;但是使用…

HashMap jdk1.7源码阅读与解析

转载自 HashMap源码阅读与解析 一、导入语 HashMap是我们最常见也是最长使用的数据结构之一&#xff0c;它的功能强大、用处广泛。而且也是面试常见的考查知识点。常见问题可能有HashMap存储结构是什么样的&#xff1f;HashMap如何放入键值对、如何获取键值对应的值以及如何…

java实现加密电话号码,有具体的加密流程注释

闲着没事做&#xff0c;正好有一位哥们让帮他看个写个逻辑题&#xff0c;我就顺便写了下&#xff01; 此题主要是加密一个数字类型的电话号码&#xff0c;具体加密流程如下&#xff1a; * 将一串数字进行加密 * 加密规则&#xff1a;先把这串数字降序&#xff0c;然后将每个…

.NET项目版本号的小随笔

【题外话】 一直以来都对.NET项目中的几个版本号&#xff08;AssemblyVersion、AssemblyFileVersion、AssemblyInformationalVersion&#xff09;以及版本号中的Revision和Build有疑问&#xff0c;今儿抽了点时间看了几篇文章&#xff0c;整理一下与大家一起分享下。 【一、Ass…

Windows.etc\hosts文件

Windows.etc\hosts文件 ZC&#xff1a;就是将 后面的项 重定位到 前面的项 1、目录&#xff1a;"C:\Windows\System32\drivers\etc" 文件&#xff1a;"C:\Windows\System32\drivers\etc\hosts" 2、c__Windows_System32_drivers_etc_hosts的作用 - Sharpe…

java实现邮件发送准备工作(前期配置)

本文主要用的邮件客户端是&#xff1a;office 2007的outlook,服务器是apache-james-2.3.2&#xff0c;首先我们来配置一下这个james服务器: 1.将james服务器解压到硬盘目录下&#xff0c;注意目录不能有中文&#xff0c;如e:盘下 2.修改apps/james/sar-inf目录下的confi…

win10关闭“Windows安全中心”功能的两种方法

win10系统怎么将windows安全中心关闭&#xff1f; 听语音 原创|浏览&#xff1a;10407|更新&#xff1a;2020-03-24 10:541 2 3 4 5 6 7 分步阅读 一些软件需要将Windows安全中心关闭。 方法/步骤 1 首先打开开始菜单。 2 在开始菜单中点击设置按钮。 3 在设置界…

ASP.NET Core 中间件Diagnostics使用

ASP.NET Core 中间件(Middleware)Diagnostics使用。对于中间件的介绍可以查看之前的文章ASP.NET Core 开发-中间件(Middleware)。 Diagnostics中间件&#xff0c;主要功能是用于报告和处理ASP.NET Core中的异常和错误信息&#xff0c;以及诊断Entity Framework核心迁移错误。 其…

使用java底层实现邮件的发送(含测试,源码)

直接上代码&#xff1a;3个类&#xff0c;两个主要的类&#xff0c;一个测试类&#xff1a; 主类&#xff08;Mail&#xff09;&#xff1a; /** * Title: Mail.java * Package org.service.impl * Description: TODO该方法的主要作用&#xff1a; * author A18ccms A18ccms_…

Java多线程:线程状态

转载自 Java多线程:线程状态 一. 线程状态类型 1. 新建状态&#xff08;New&#xff09;&#xff1a;新创建了一个线程对象。 2. 就绪状态&#xff08;Runnable&#xff09;&#xff1a;线程对象创建后&#xff0c;其他线程调用了该对象的start()方法。该状态的线程位于可运行…

Win10怎么关闭开机启动项

Win10怎么关闭开机启动项 我们可以首先打开电脑的运行对话框&#xff0c;按下键盘的WINR组合键&#xff0c;打开运行。 然后这里我们在运行对话框中输入命令msconfig确定&#xff0c;打开系统配置程序。 系统配置窗口&#xff0c;启动里面点击这里的任务管理器打开。 这时…

TypeScript 2.1发布

TypeScript是微软开发的一个JavaScript的超集&#xff0c;提供了最新的JavaScript特性以及可选的静态类型。近日&#xff0c;TypeScript 2.1发布。该版本提供了功能更为强大的类型检查器&#xff0c;并且让开发人员可以编写出更简洁的代码。以下是该版本带来的主要新特性&#…

使用spring实现邮件的发送(含测试,源码,注释)

此篇主要讲的是使用spring配置实现邮件发送&#xff0c;与之前的底层实现简便了不少&#xff0c;只需要几个配置就可以了&#xff0c;那么请往下看&#xff1a; 先写个接口 /** * Title: IMailserdService.java * Package org.service * Description: TODO该方法的主要作用&a…

你当前无权访问该文件夹 解决你当前无权访问该文件夹拒绝你访问该文件夹

我 这样就完成了 http://www.xitonghe.com/jiaocheng/windows7-5642.html https://jingyan.baidu.com/article/4b52d702aa01b3fc5c774b1b.html Win10正式版提示你当前无权访问该文件夹怎么办 https://jingyan.baidu.com/article/4b52d702aa01b3fc5c774b1b.html 1407345人看了…

SpringBoot+Vue博客系统---后端接口开发

Java后端接口开发 从零开始搭建一个项目骨架&#xff0c;最好选择合适&#xff0c;熟悉的技术&#xff0c;并且在未来易拓展&#xff0c;适合微服务化体系等。所以一般以Springboot作为我们的框架基础&#xff0c;这是离不开的了。 然后数据层&#xff0c;我们常用的是Mybati…

图说世界编程语言排行

TIOBE编程语言社区排行榜是编程语言流行趋势的一个指标&#xff0c;每月更新&#xff0c;这份排行榜排名基于互联网上有经验的程序员、课程和第三方厂商的数量。排名使用著名的搜索引擎&#xff08;诸如Google、MSN、Yahoo!、Wikipedia、YouTube以及Baidu等&#xff09;进行计算…

中国有超级计算机的大学,计算机专业排名看超算实力,ASC竞赛五大高校排名,中山大学第一...

ASC竞赛五大高校计算机专业的实力主要体现在算法与编程的逻辑运算上&#xff0c;因此计算机专业必须掌握大量基础数学知识&#xff0c;甚至很多是离散数学、模糊数学等人工智能逻辑数学&#xff0c;简单的程序软件应用和O2O程序实现其实都不是计算机专业实力的体现&#xff0c;…

IntelliJ IDEA设置JDK版本

IntelliJ IDEA设置JDK版本 临渊行 2019-06-13 13:59:13 46888 收藏 30 分类专栏&#xff1a; 这里有个坑 版权 一、背景 即使我电脑安装的JDK版本是8&#xff0c;然而在idea运行中常常提示xxjdk1.5已过时之类的&#xff0c;why?明明是我装的JDK8啊 二、解决 鼠标点击f…