在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. 【竞价网站绝技】根据访客ip,页面显示访客所属城市的html代码(借用YY IP地址库)

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  2. Selenium+PyCharm环境搭建

    一.首先安装python并配置好环境变量 二.安装selenium 安装文件夹在安装的python文件夹下,例:D:\Program\python\Lib\site-packages\selenium ...

  3. PHP安装+使用

        curl -s http://php-osx.liip.ch/install.sh | bash -s 5.4       ...... Extracting usr/local/php5-5 ...

  4. C#运行时通过字符串实例化类对象

    备忘,记个C#版本. using System; using System.Collections.Generic; using System.Linq; using System.Text; usi ...

  5. vue监听滚动事件-元素固定位置显示

    1.监听滚动事件 用VUE写一个在控制台打印当前的scrollTop用来测试是否执行: mounted () { window.addEventListener('scroll', this.hand ...

  6. Tensoflow API笔记(N) 设备指定

        tf.device是tf.Graph.device()的一个包装,是一个用于指定新创建的操作(operation)的默认设备的环境管理器.参数为device_name_or_function, ...

  7. 【repost】 JS变量重复声明以及忽略var 声明的问题及其背后的原理

    JS的容错率很高,一些其他语言常见的小错误JS都能大度得包容,比如给一个方法传入超出预计的参数.在声明变量之前使用该变量(变量的声明提升解决了这个问题)等等,这里我们就要解剖一下JS变量重复声明以及当 ...

  8. 阿里云Centos+Django+Nginx+uWSGI

    针对系统中自带的Python2.7版本 1.安装python-devel yum install python-devel 2.安装uwsgi pip install uwsgi 3.测试uwsgi是 ...

  9. python基础自学 第四天

    break和continue break:某一条件满足,退出循环,不在执行后续重复代码 continue:某一条件满足时,不执行后续重复的代码 注意:在循环中,如果使用continue这个关键字,使用 ...

  10. Linux(lamp安装)

    网络配置 1. 配置ip地址和子网掩码 编辑配置文件: > cd  /etc/sysconfig/network-scripts > cp  ifcfg-eth0  ./ifcfg-eth ...