在JDK1.7及以前中,如果在并发环境中使用HashMap保存数据,有可能会产生死循环的问题,造成cpu的使用率飙升。之所以会发生该问题,实际上就是因为HashMap中的扩容问题。

HashMap的实现实际上是一个数组+链表的实现(JDK1.8中当链表长度达到一定值会转化为红黑树),当HashMap中保存的值超过阈值时将会进行一次扩容操作,并发环境下可能存在一个线程发现HashMap容量不够需要扩容,而在这个过程中,另外一个线程也刚好进行扩容操作,这时就有可能造成死循环的问题。扩容操作一般是在调用put(...)方法时进行的,put时会对容量进行检查。如果在扩容是链表中产生一个环形链表,那么在使用get(...)获取数据时将可能产生死循环。

    //进行扩容时调用的方法
void resize(int newCapacity) {
Entry[] oldTable = table;//保存旧的数组
int oldCapacity = oldTable.length;//保存旧的容量
if (oldCapacity == MAXIMUM_CAPACITY) {//无法再进行扩容
threshold = Integer.MAX_VALUE;
return;
} Entry[] newTable = new Entry[newCapacity];
//将原数组中的元素迁移到扩容后的数组中
//死循环就是在这个方法中产生的
transfer(newTable, initHashSeedAsNeeded(newCapacity));
table = newTable;
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
} void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
for (Entry<K,V> e : table) {
while(null != e) {
Entry<K,V> next = e.next;
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
}
}
}

在并发环境中,多个线程并不是一起执行的,而是由cpu来调度,任何线程在任意时刻都有可能停下来,当线程停下来时状态实际上被保存在栈帧中,下一次被cpu分配到执行权时从读取当前状态开始继续执行,对于被cpu挂起的线程在挂起这段时间相当于是"时间暂停"了。明白了这些接下来我们模拟死循环出现的情况,实际上这种情况是很不容易出现的,因为需要的条件较多,但是如果我们线上环境执行频率很高的话产生这种情况就显得比较容易了,一旦产生一次那就是灾难,除了修改代码重启服务器基本没别的解决方法。

假设当前HashMap中数组长度为1,并且只保存了两个值(设置的值都是为了产生死循环),如下图,改图表示一个长度为1的数组,保存值为1和3的两个值:

现在假设两个线程都在进行扩容操作,线程1刚开始,当走到Entry<K,V> next = e.next;时线程挂起,cpu被分配给线程2。

线程2在cpu分配的执行时间中对HashMap操作后变成下图所示。扩容后,数组长度变为2,由于扩容是重新插入(头插法)的原因,值得顺序变了,现在value=3持有value=1的引用。此时线程2挂起,cpu切换到线程1。之前保存的状态中e的值为value=1的entry且e包含value=3的值得引用。

线程1继续之前的操作:

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

经过两次循环后:

看起来和正常的没有什么区别,理论上来说,正常情况下由于value=3的entry的next为null此时应该跳出循环,但是问题在于之前的线程2使得value=3持有value=1的引用,这时还会在进行一次循环:

//此时e = value(1)
Entry<K,V> next = e.next; //next=null
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i]; //e.next=value(3) 死循环了
newTable[i] = e;
e = next;

由上可知此时value(3).next=value(1) 并且value(1).next=value(3),产生了环形链表。

如果之后调用get(...)方法,能找到还好,如果查找的值不存在,那么get方法会在环形链表处一直循环无法退出,只能重启服务器了...

HashMap在JDK1.7中可能出现的并发问题的更多相关文章

  1. HashMap在JDK1.8中并发操作,代码测试以及源码分析

    HashMap在JDK1.8中并发操作不会出现死循环,只会出现缺数据.测试如下: package JDKSource; import java.util.HashMap; import java.ut ...

  2. 牛客网Java刷题知识点之HashMap的实现原理、HashMap的存储结构、HashMap在JDK1.6、JDK1.7、JDK1.8之间的差异以及带来的性能影响

    不多说,直接上干货! 福利 => 每天都推送 欢迎大家,关注微信扫码并加入我的4个微信公众号:   大数据躺过的坑      Java从入门到架构师      人工智能躺过的坑          ...

  3. Java并发编程总结4——ConcurrentHashMap在jdk1.8中的改进(转)

    一.简单回顾ConcurrentHashMap在jdk1.7中的设计 先简单看下ConcurrentHashMap类在jdk1.7中的设计,其基本结构如图所示: 每一个segment都是一个HashE ...

  4. Java并发编程总结4——ConcurrentHashMap在jdk1.8中的改进

    一.简单回顾ConcurrentHashMap在jdk1.7中的设计 先简单看下ConcurrentHashMap类在jdk1.7中的设计,其基本结构如图所示: 每一个segment都是一个HashE ...

  5. Jdk1.8中的HashMap实现原理

    HashMap概述 HashMap是基于哈希表的Map接口的非同步实现.此实现提供所有可选的映射操作,并允许使用null值和null键.此类不保证映射的顺序,特别是它不保证该顺序恒久不变. HashM ...

  6. 【不做标题党,只做纯干货】HashMap在jdk1.7和1.8中的实现

     同步首发:http://www.yuanrengu.com/index.php/20181106.html Java集合类的源码是深入学习Java非常好的素材,源码里很多优雅的写法和思路,会让人叹为 ...

  7. 【1】Jdk1.8中的HashMap实现原理

    HashMap概述 HashMap是基于哈希表的Map接口的非同步实现.此实现提供所有可选的映射操作,并允许使用null值和null键.此类不保证映射的顺序,特别是它不保证该顺序恒久不变. 内部实现 ...

  8. JDK1.7中HashMap死环问题及JDK1.8中对HashMap的优化源码详解

    一.JDK1.7中HashMap扩容死锁问题 我们首先来看一下JDK1.7中put方法的源码 我们打开addEntry方法如下,它会判断数组当前容量是否已经超过的阈值,例如假设当前的数组容量是16,加 ...

  9. JDK1.7 中的HashMap源码分析

    一.源码地址: 源码地址:http://docs.oracle.com/javase/7/docs/api/ 二.数据结构 JDK1.7中采用数组+链表的形式,HashMap是一个Entry<K ...

随机推荐

  1. 【Mybatis】MyBatis之Sql配置文件的使用(四)

    上一章[Mybatis]MyBatis对表执行CRUD操作(三),已经讲了基本操作,本章介绍Sql配置文件中常用功能 1.插入返回主键 2.参数值的获取方式 3.resultMap使用 插入返回主键 ...

  2. 【Selenium】【BugList10】smtp发送邮件问题汇总:550/535/554

    [场景] 通过126邮箱向QQ邮箱发送HTML格式邮件 [代码1] from email.mime.text import MIMEText from email.header import Head ...

  3. 你了解栈溢出StackOverFloweExeption的原理吗?

    StackOverflowException的常见几种引起的方式 1.类的相互引用 2.方法的循环调用 3.属性Set方法的死循环调用 class Program : IProgram { IPers ...

  4. Win7 VS2017编译Godot3.0.2和2.1.4

    千呼万唤屎出来,Godot终于发布3.0版本的源码了,今天是2018年3月17日,自去年接触过后,一直没事就刷刷Gayhub,看看3.0什么时候更新,刷了一年也没结果. 今天上Youtube无意中搜了 ...

  5. TCP的三次握手 与 四次挥手

    TCP的连接建立是一个三次握手过程,目的是为了通信双方确认开始序号,以便后续通信的有序进行 主要步骤: 服务器一定处于Listen状态,否则客户端发过来的连接会被拒绝.注:服务器和客户端的角色是相对的 ...

  6. 关于Asp.net事件,如何在触发子控件的事件时,同步触发父页面的事件

    对页面引用自定义控件后,通过绑定自定义事件,页面绑定子控件的事件,在子控件做了某些修改动作后,如何同步操作父页面的方法:下面我煮了个栗子,同学们可以来尝一尝试一试 a.aspx 引用 UserCont ...

  7. 京东Alpha平台开发笔记系列(二)

    第一篇博文简单讲了一下京东Alpha平台与个人idea技能,本篇将讲解Alpha平台与个人开发需要的一些知识,下面开篇 ——>>> 上图就是京东Alpha技能平台的首页,Skill平 ...

  8. Python if __name__ == '__main__':

    python属于脚本语言,只能逐行运行, if __name__ == '__main__':这句相当于main(),即首先执行这条语句.

  9. [译]迁移到新的 React Context Api

    随着 React 16.3.0 的发布,context api 也有了很大的更新.我已经从旧版的 api 更新到了新版.这里就分享一下我(作者)的心得体会. 回顾 下面是一个展示如何使用旧版 api ...

  10. How Does Closure Work in Javascript?

    Simply, closure is the scope that it can visite and operate the variables outside of the function wh ...