HashMap 死循环的探究
大家都知道,HashMap采用链表解决Hash冲突,具体的HashMap的分析可以参考一下http://zhangshixi.iteye.com/blog/672697 的分析。因为是链表结构,那么就很容易形成闭合的链路,这样在循环的时候就会产生死循环。但是,我好奇的是,这种闭合的链路是如何形成的呢。在单线程情况下,只有一个线程对HashMap的数据结构进行操作,是不可能产生闭合的回路的。那就只有在多线程并发的情况下才会出现这种情况,那就是在put操作的时候,如果size>initialCapacity*loadFactor,那么这时候HashMap就会进行rehash操作,随之HashMap的结构就会发生翻天覆地的变化。很有可能就是在两个线程在这个时候同时触发了rehash操作,产生了闭合的回路。下面我们从源码中一步一步地分析这种回路是如何产生的。先看一下put操作:
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
//存在key,则替换掉旧的value
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
//table[i]为空,这时直接生成一个新的entry放在table[i]上
addEntry(hash, key, value, i);
return null;
}
addEntry操作:
void addEntry(int hash, K key, V value, int bucketIndex) {
ry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
if (size++ >= threshold)
resize(2 * table.length);
}
可以看到,如果现在size已经超过了threshold,那么就要进行resize操作:
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];
//将旧的Entry数组的数据转移到新的Entry数组上
transfer(newTable);
table = newTable;
threshold = (int)(newCapacity * loadFactor);
}
看一下transfer操作,闭合的回路就是在这里产生的:
void transfer(Entry[] newTable) {
Entry[] src = table;
int newCapacity = newTable.length;
/*
* 在转换的过程中,HashMap相当于是把原来链表上元素的的顺序颠倒了。
* 比如说 原来某一个Entry[i]上链表的顺序是e1->e2->null,那么经过操作之后
* 就变成了e2->e1->null
*/
for (int j = 0; j < src.length; j++) {
Entry<K,V> e = src[j];
if (e != null) {
src[j] = null;
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);
}
}
}
那么回路究竟是如何产生的呢,问题就出在next=e.next这个地方,在多线程并发的环境下,为了便于分析,我们假设就两个线程P1,P2。src[i]的链表顺序是e1->e2->null。我们分别线程P1,P2的执行情况。
首先,P1,和P2进入到了for循环中,这时候在线程p1和p2中,局部变量分别如下:
| e | next | |
| P1 | e1 | e2 |
| P2 | e1 | e2 |
此时两个Entry的顺序是依然是最开始的状态e1->e2->null, 但是此时p1可能某些原因线程暂停了,p2则继续执行,并执行完了do while循环。这时候Entry的顺序就变成了e2->e1->null。在等到P2执行完之后,可能p1才继续执行,这时候在P1线程中局部变量e的值为e1,next的值为e2(注意此时两个元素在内存中的顺序变成了e2->e1->null),下面P1线程进入了do while循环。这时候P1线程在新的Entry数组中找到e1的位置,
e.next = newTable[i];
newTable[i] = e;
下面会把next赋值给e,这时候e的值成为了e2,继续下一次循环,这时候
| e | next | |
| P1 | e2 | e1 |
e2->next=e1,这个是线程P2的"功劳"。程序执行完这次循环之后,e=e1,
继续第三次循环,这时候根据算法,就会进行e1->next=e2。
这样在线程P1中执行了 e1->next=e2,在线程P2中执行了 e2->next=e1,这样就形成了一个环。在get操作的时候,next值永远不为null,造成了死循环。
实际上,刚开始我碰到这个说法的时候,还被吓了一跳,HashMap怎么还会出现这个问题呢,仔细分析一下,这个问题再高并发的场景下是很容易出现的。Sun的工程师建议在这样的场景下应采用ConcurrentHashMap。具体参考http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6423457 。
虽然这个问题再平时的工作中还没有遇到,但是以后需要注意。要在不同的场景下选择合适的类,规避类似HashMap这种死循环的问题。
HashMap 死循环的探究的更多相关文章
- 多线程情况下HashMap死循环的问题
1.多线程put操作后,get操作导致死循环. 2.多线程put非null元素后,get操作得到null值. 3.多线程put操作,导致元素丢失. 死循环场景重现 下面我用一段简单的DEMO模拟Has ...
- 面试官:HashMap死循环形成的原因是什么?
介绍 HashMap实现原理 之前的文章已经分析了HashMap在JDK1.7的实现,这篇文章就只分析HashMap死循环形成的原因 死循环形成是在扩容转移元素的时候发生的 void resize(i ...
- 并发场景下HashMap死循环导致CPU100%的问题
参考链接:并发场景下HashMap死循环导致CPU100%的问题
- jdk7和8中关于HashMap和concurrentHashMap的扩容过程总结,以及HashMap死循环
题外话:为什么要hashcode进行spread? 充分使用key.hashCode()的高16位信息,保证hash分布更分散, 扩容操作是新建2倍于原表大小的新表,并将原表结点拷贝一份放在新表中,对 ...
- 一、基础篇--1.2Java集合-HashMap死循环问题
为什么HashMap会出现死循环 首先,我们知道java的HashMap不是线程安全的.多线程下应该使用ConcurrentHashMap. HashMap底层是基于动态数组和单向链表(JDK1.7, ...
- HashMap遍历方式探究
HashMap的遍历有两种常用的方法,那就是使用keyset及entryset来进行遍历,但两者的遍历速度是有差别的,下面请看实例: package com.HashMap.Test; import ...
- HashMap并发下死循环问题解析
首先小伙伴要明确:死循环问题在JDK 1.8 之前是存在的,JDK 1.8 通过增加loHead和loTail进行了修复. 在JDK 1.7及之前 HashMap在并发情况下导致循环问题,致使服务器c ...
- 多线程下HashMap的死循环问题
多线程下[HashMap]的问题: 1.多线程put操作后,get操作导致死循环.2.多线程put非NULL元素后,get操作得到NULL值.3.多线程put操作,导致元素丢失. 本次主要关注[Has ...
- HashMap多线程死循环问题
HashMap通常会用一个指针数组(假设为table[])来做分散所有的key,当一个key被加入时,会通过Hash算法通过key算出这个数组的下标i,然后就把这个<key, value> ...
随机推荐
- Monkey源码分析之事件源
上一篇文章<Monkey源码分析之运行流程>给出了monkey运行的整个流程,让我们有一个概貌,那么往后的文章我们会尝试进一步的阐述相关的一些知识点. 这里先把整个monkey类的结构图给 ...
- Hybrid
“榕树下·那年”移动app ( hybrid ) 开发总结 榕树下网站本身的技术人员并不多,所以app开发的任务就到了母公司盛大文学这边. 盛大文学无线业务中心负责这次具体开发任务. ...
- windows 7 下快速搭建php环境(windows7+IIS7+php+mysql)
原文:windows 7 下快速搭建php环境(windows7+IIS7+php+mysql) 1).采用理由: 优点:最大化的桌面图形化操作系统,可维护性优秀.基于IIS v6.0/v7.0(20 ...
- 2.4 LINQ中使用where子句指定筛选条件
本篇讲解的内容有: 使用where筛选过滤LINQ查询 带逻辑的where筛选 多个where筛选子句 [1.使用where筛选过滤LINQ查询] 通常一个LINQ查询不会如前面的示例代码这么简单,经 ...
- Oracle 跨库 查询 复制表数据 分布式查询
方法一: 在眼下绝大部分数据库有分布式查询的须要.以下简单的介绍怎样在oracle中配置实现跨库訪问. 比方如今有2个数据库服务器,安装了2个数据库.数据库server A和B.如今来实如今A库中訪问 ...
- mediawiki在windows下的安装
mediawiki在windows下的安装 对于刚接触wiki的朋友们来说,配置一个服务器环境,安装并运行mediawiki是一件很麻烦的事情,在这里,我尽量用通俗易懂的语言,介绍mw(mediawi ...
- 查询select
--------------select查询-------------------查询所有信息(方法一)select * from stuinfo --*号代表所有列--查询所有信息(方法二)sele ...
- 轻量级IOC框架:Ninject
Ninject 学习杂记 - liucy 时间2014-03-08 00:26:00 博客园-所有随笔区原文 http://www.cnblogs.com/liucy1898/p/3587455.h ...
- JS数量输入控件
JS数量输入控件 很早看到kissy首页 有数量输入控件,就随便看了下功能 感觉也不怎么难 所以也就试着自己也做了一个, 当然基本的功能和他们的一样,只是用了自己的编码思想来解决这么一个问题.特此给大 ...
- Push Notification总结系列(二)
Push Notification系列概括: 1.Push Notification简介和证书说明及生成配置 2.Push Notification的iOS处理代码和Provider详解 3.Push ...