上一章有写risize()代码,这一章我们从risize()开始进行讲解。

一、hash表迁移

新建一个更大尺寸的 hash 表,然后把数据从老的 Hash 表中迁移到新的 Hash 表中

resize#源码:

void resize(int newCapacity) {Entry[] oldTable = table;int oldCapacity = oldTable.length;......// 创建一个新的 Hash TableEntry[] newTable = new Entry[newCapacity];// 将 Old Hash Table 上的数据迁移到 New Hash Table 上transfer(newTable);table = newTable;threshold = (int)(newCapacity * loadFactor);
}

resize#方法中引用了transfer#方法,transfer#方法源码:

void transfer(Entry[] newTable) {Entry[] src = table;int newCapacity = newTable.length;//下面这段代码的意思是://  从OldTable里摘一个元素出来,然后放到NewTable中for (int j = 0; j < src.length; j++) {Entry<K,V> e = src[j];if (e != null) {src[j] = null;//释放旧Entry数组的对象引用(for循环后,旧的Entry数组不再引用任何对象;方便垃圾回收do {Entry<K,V> next = e.next;int i = indexFor(e.hash, newCapacity);//!!重新计算每个元素在数组中的位置    e.next = newTable[i];//标记[1]  newTable[i] = e;//将元素放在数组上    e = next; //访问下一个Entry链上的元素} while (e != null);}}
}

该方法实现的机制就是将每个链表转化到新链表,并且链表中的位置发生反转,而这在多线程情况下是很容易造成链表回路,从而发生 get() 死循环。所以只要保证建新链时还是按照原来的顺序的话就不会产生循环(JDK 8 的改进)

二、正常的 ReHash 的过程

我们先来看下单线程情况下,正常的rehash过程

1、假设我们的hash算法是简单的key mod一下表的大小(即数组的长度)。
2、最上面是old hash表,其中HASH表的size=2,所以key=3,5,7在mod 2 以后都冲突在table[1]这个位置上了。
3、接下来HASH表扩容,resize=4,然后所有的<key,value>重新进行散列分布,过程如下:

三、并发下的 Rehash

(1)假设有两个线程

do {
    Entry<K,V> next = e.next; //  假设线程一执行到这里就被调度挂起了
    int i = indexFor(e.hash, newCapacity);
    e.next = newTable[i];
    newTable[i] = e;
    e = next;
} while (e != null);

注意: 这里非常重要

e.next = newTable[i];
newTable[i] = e;
e = next;

这三行代码解释:
将newTable[i]上的值赋给e元素的next属性(如果是第一次循环中,newTable[i]是null,即第一个元素的next节点赋值为null),e属性再赋值给newTable[i],这样newTable[i]上的链表新node都会靠前,之前的元素相当于后移了。这样链表的顺序就倒过来了,就是我们说的前插法。

而线程二执行完成了。于是有下面的这个样子

注意,因为 Thread1 的 e 指向了 key(3),而 next 指向了 key(7),其在线程二 rehash 后,指向了线程二重组后的链表。可以看到链表的顺序被反转

(2)线程一被调度回来执行

  • 先是执行 newTalbe[i] = e;
  • 然后是 e = next,导致了 e 指向了 key(7)
  • 而下一次循环的 next = e.next 导致了 next 指向了 key(3)

(3)线程一接着工作。把 key(7) 摘下来,放到 newTable[i] 的第一个,然后把 e 和 next 往下移

4)环形链接出现
e.next = newTable[i] 导致 key(3).next 指向了 key(7)
此时的 key(7).next 已经指向了 key(3), 环形链表就这样出现了

四、之前的疑问与解答

疑问:线程一被调度回来执行时,第一次执行e.next = newTable[i]时; 此时不应该是 e.next = 7吗?
如上图,此时newTable[i]3的位置上,不是已经有值了?我感觉,这段代码,会一直死循环下去;
: 这个在上面解释三行代码的时候已经解释过了。
网上另有答案,与我写的是一个意思:newTable[]是在每个线程里new出来的,属于线程私有的;所以,第一次,里面并没有值(或者是随机的值);

疑问:循环产生的原因:
:我是这么认为的,之所以会产生这种死循环,①是每次扩容时,链表都发生了倒置,导致原先的next都存的是它们前一个元素。为发生环形链表埋下伏笔;②链表插入的方式,其采用的是头插入,每次插入新元素,都是放在最前面的; 不过也正式因为其采用头插入,所以才会发送了链表倒置;这是不是告诉我,假设我以后用到链表时,最好不要用头插入的方式,来新增元素。

五、JDK 8 的改进

JDK 8 中采用的是位桶 + 链表/红黑树的方式,当某个位桶的链表的长度超过 8 的时候,这个链表就将转换成红黑树

HashMap 不会因为多线程 put 导致死循环(JDK 8 用 head 和 tail 来保证链表的顺序和之前一样;JDK 7 rehash 会倒置链表元素),但是还会有数据丢失等弊端(并发本身的问题)。因此多线程情况下还是建议使用 ConcurrentHashMap

六、为什么线程不安全

HashMap 在并发时可能出现的问题主要是两方面:

  • 如果多个线程同时使用 put 方法添加元素,而且假设正好存在两个 put 的 key 发生了碰撞(根据 hash 值计算的 bucket 一样),那么根据 HashMap 的实现,这两个 key 会添加到数组的同一个位置,这样最终就会发生其中一个线程 put 的数据被覆盖

  • 如果多个线程同时检测到元素个数超过数组大小 * loadFactor,这样就会发生多个线程同时对 Node 数组进行扩容,都在重新计算元素位置以及复制数据,但是最终只有一个线程扩容后的数组会赋给 table,也就是说其他线程的都会丢失,并且各自线程 put 的数据也丢失

HashMap死循环讲解(JDK1.8 之前)相关推荐

  1. java 中向文本写入和读取hashmap_就靠这一篇HashMap的讲解,我与头条面试官聊了一个小时。...

    预备知识 位运算知识(更多资料私信"学习"免费获取) 位运算操作是由处理器支持的底层操作,底层硬件只支持01这样的数字,因此位运算运行速度很快.尽管现代计算机处理器拥有了更长的指令 ...

  2. HashMap 死循环

    HashMap 死循环 多线程扩容的时候,两个分别指向 e1 next 一个线程扩容完成之后,变成下图的形式 线程二没有感知,还是扩容,它扩容的时候,按照 3 2 ,的顺序来进行 最终会得到一个环状的 ...

  3. 靠一个HashMap的讲解打动了头条面试官,我的秘诀是

    最近收集了一份github标星81.6k的Java面试突击手册,文末查看 关注 转发+转发+转发 私信回复关键词 [学习]即可获取~ 预备知识 位运算知识 位运算操作是由处理器支持的底层操作,底层硬件 ...

  4. 分析多线程下jdk1.8之前hashmap的put方法造成死循环而jdk1.8之后如何解决这个死循环

    美团技术博客 图解HashMap(一) HashMap多线程死循环问题(调试查看) HashMap中傻傻分不清楚的那些概念 Java的HashMap是非线程安全的,在多线程下应该使用Concurren ...

  5. hashmap put过程_阿里十年技术大咖,教你如何分析1.7中HashMap死循环

    在多线程环境下,使用HashMap进行put操作会引起死循环,导致CPU利用率接近100%,HashMap在并发执行put操作时会引起死循环,是因为多线程会导致HashMap的Entry链表 形成环形 ...

  6. Java程序员春招三面蚂蚁金服,1-7中HashMap死循环分析

    现在hashmap中有三个元素,Hash表的size=2, 所以key = 3, 7, 5,在mod 2以后都冲突在table[1]这里了. 按照方法中的代码 对table[1]中的链表来说,进入wh ...

  7. JDK 1.7及之前——HashMap死循环问题解析

    在淘宝内网里看到同事发了贴说了一个CPU被100%的线上故障,并且这个事发生了很多次,原因是在Java语言在并发情况下使用HashMap造成Race Condition,从而导致死循环.这个事情我4. ...

  8. java8 hashmap 死循环_踩坑了,JDK8中HashMap依然会死循环!

    作者:Aaron_涛 原文:blog.csdn.net/qq_33330687/article/details/101479385 是否你听说过JDK8之后HashMap已经解决的扩容死循环的问题,虽 ...

  9. java hashmap hash算法,jdk1.8 中 HashMap 的 hash 算法和数组寻址

    开篇 本文基于 jdk1.8 讲述 HashMap 的 hash 算法,但是不会详细介绍其他相关内容(比如用法,底层数据结构).所以必须事先知晓下面几点: HashMap 的底层数据结构是数组,在数组 ...

  10. 【Map】——HashMap死循环

    前言 在<Map--HashMap>中我们详细介绍了HashMap的使用,HashMap是一个数组链表,当一个key/Value对被加入时,首先会通过Hash算法定位出这个键值对要被放入的 ...

最新文章

  1. WannaCry 不相信眼泪 它需要你的安全防御与响应能力
  2. 【JavaScript】JavaScript基础-变量、运算符与控制语句
  3. 如何阅读苹果开发文档
  4. rhel6 mysql replication
  5. java 怎么快速找到实现类_JAVA懒开发:FreeMarker快速实现类的增删改查接口
  6. python读取配置文件使用_python 使用 ConfigParser 读取和修改INI配置文件
  7. Spring Boot 快速集成第三方登录功能
  8. PgSQL · 应用案例 · 逻辑订阅给业务架构带来了什么?
  9. jQuery.validator.addMethod 自定义验证方法
  10. Javascript内置对象之Date对象与HTML BOM
  11. IAR软件安装图文教程
  12. gnome黑屏 ubuntu_Ubuntu 16.04+GTX970 黑屏无法安装解决方法
  13. java 分布式日志_打造分布式日志收集系统
  14. 原生js实现新年倒计时
  15. git实用技巧:将多次commit合并为一次
  16. pandas将df赋值到另一个df_pandas基础
  17. 运筹优化学习07:Lingo的 @if 函数的使用方法
  18. 【离散数学笔记】逻辑运算之吸收律
  19. 论文阅读《Semantic Relation Reasoning for Shot-Stable Few-Shot Object Detection》
  20. 15V转5V稳压芯片选型表

热门文章

  1. MATLAB--黄金分割法
  2. 计算机初级技能词,计算机领域英语常用词汇初级.doc
  3. cpp调用python_从python ctypes调用CPP函数
  4. Presto 安装与部署
  5. B-S期权定价模型 Black Scholdz
  6. 20. 有效的括号 python
  7. 第四章OFDM(1)
  8. 超标量体系结构_计算机体系结构——以多发射和静态调度来开发ILP
  9. 凸优化第六章逼近与拟合 6.3正则化逼近
  10. Springboot配置devtools实现热部署