分析多线程并发写HashMap线程被hang住的原因

转载 2012年03月21日 10:39:59

public class TestLock {  

 private final HashMap map = new HashMap();  

 public TestLock() {     final Thread t1 = new Thread() {      

  @Override       public void run() {         for(int i=0; i<500000; i++) {          

  map.put(new Integer(i), i);        

}        

 System.out.println("t1 over");      

 }    

 };    

 final Thread t2 = new Thread() {      

@Override       public void run() {        

for(int i=0; i<500000; i++) {          

map.put(new Integer(i), i);        

}        

System.out.println("t2 over");      

}    

};    

t1.start();    

t2.start();  

 }    

public static void main(final String[] args) {    

new TestLock();  

}

 }

 

 

 

就是启了两个线程,不断的往一个非线程安全的HashMap中put内容,put的内容很简单,key和value都是从0自增的整数(这个put的内容做的并不好,以致于后来干扰了我分析问题的思路)。对HashMap做并发写操作,我原以为只不过会产生脏数据的情况,但反复运行这个程序,会出现线程t1、t2被hang住的情况,多数情况下是一个线程被hang住另一个成功结束,偶尔会两个线程都被hang住。说到这里,你如果觉得不好好学习ConcurrentHashMap而在这瞎折腾就手下留情跳过吧。
好吧,分析下HashMap的put函数源码看看问题出在哪,这里就罗列出相关代码(jdk1.6):

 

 

public V put(final K key, final V value) {    

 if (key == null) {      

 return putForNullKey(value);    

 }    

 final int hash = hash(key.hashCode());    

 final int i = indexFor(hash, table.length);    

 for (Entry<K,V> e = table[i]; e != null; e = e.next) {

//@标记1       Object k;      

if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {        

final V oldValue = e.value;        

 e.value = value;        

e.recordAccess(this);        

return oldValue;      

 }    

}      

modCount++;    

addEntry(hash, key, value, i);    

return null;  

}  

void addEntry(final int hash, final K key, final V value, final int bucketIndex) {    

 final Entry<K,V> e = table[bucketIndex];    

table[bucketIndex] = new Entry<K,V>(hash, key, value, e);    

 if (size++ >= threshold) {      

resize(2 * table.length);   

  }  

}  

void resize(final int newCapacity) {    

final Entry[] oldTable = table;    

final int oldCapacity = oldTable.length;    

if (oldCapacity == MAXIMUM_CAPACITY) {      

 threshold = Integer.MAX_VALUE;      

 return;    

}      

final Entry[] newTable = new Entry[newCapacity];     t

 

ransfer(newTable);    

 table = newTable;    

threshold = (int)(newCapacity * loadFactor);  

}   void transfer(final Entry[] newTable) {    

final Entry[] src = table;    

final int newCapacity = newTable.length;    

final long time1 = System.currentTimeMillis();    

 for (int j = 0; j < src.length; j++) {      

Entry<K,V> e = src[j];      

 if (e != null) {        

src[j] = null;        

 int k=0;//@标记2        

do {          

 final Entry<K,V> next = e.next;          

final int i = indexFor(e.hash, newCapacity);          

 e.next = newTable[i];          

newTable[i] = e;          

 e = next;          

 if (k++ > 1000) {

//@标记3            

System.out.println(Thread.currentThread().getName()+                 ",e==next:"+(e==e.next)+",e==next.next:"+(e==e.next.next)+                 ",e:"+e+",next:"+e.next+",eq:"+e.equals(e.next));             try {               Thread.sleep(2000);             } catch (final Exception e2) {             }             }         } while (e != null);       }     }   }

 

通过jconsole(或者thread dump),可以看到线程停在了transfer方法的while循环处。这个transfer方法的作用是,当Map中元素数超过阈值需要resize时,它负责把原Map中的元素映射到新Map中。我修改了HashMap,加上了@标记2和@标记3的代码片断,以打印出死循环时的状态,结果死循环线程总是出现类似这样的输出:“Thread-1,e==next:false,e==next.next:true,e:108928=108928,next:108928=108928,eq:true”。
这个输出表明:
1)这个Entry链中的两个Entry之间的关系是:e=e.next.next,造成死循环。
2)e.equals(e.next),但e!=e.next。因为测试例子中两个线程put的内容一样,并发时可能同一个key被保存了多个value,这种错误是在addEntry函数产生的,但这和线程死循环没有关系。

接下来就分析transfer中那个while循环了。先所说这个循环正常的功能:src[j]保存的是映射成同一个hash值的多个Entry的链表,这个src[j]可能为null,可能只有一个Entry,也可能由多个Entry链接起来。假设是多个Entry,原来的链是(src[j]=a)->b(也就是src[j]=a,a.next=b,b.next=null),经过while处理后得到了(newTable[i]=b)->a。也就是说,把链表的next关系反向了。

再看看这个while中可能在多线程情况下引起问题的语句。针对两个线程t1和t2,这里它们可能的产生问题的执行序列做些个人分析:

1)假设同一个Entry列表[e->f->...],t1先到,t2后到并都走到while中。t1执行“e.next = newTable[i];newTable[i] = e;”这使得e.next=null(初始的newTable[i]为null),newTable[i]指向了e。这时t2执行了“e.next = newTable[i];newTable[i] = e;”,这使得e.next=e,e死循环了。因为循环开始处的“final Entry next = e.next;”,尽管e自己死循环了,在最后的“e = next;”后,两个线程都会跳过e继续执行下去。

2)在while中逐个遍历Entry链表中的Entry而把next关系反向时,newTable[i]成为了被交换的引用,可疑的语句在于“e.next = newTable[i];”。假设链表e->f->g被t1处理成e<-f<-g,newTable[i]指向了g,这时t2进来了,它一执行“e.next = newTable[i];”就使得e->g,造成了死循环。所以,理论上来说,死循环的Entry个数可能很多。尽管产生了死循环,但是t1执行到了死循环的右边,所以是会继续执行下去的,而t2如果执行“final Entry next = e.next;”的next为null,则也会继续执行下去,否则就进入了死循环。

3)似乎情况会更复杂,因为即便线程跳出了死循环,它下一次做resize进入transfer时,有可能因为之前的死循环Entry链表而被hang住(似乎是一定会被hang住)。也有可能,在put检查Entry链表时(@标记1),因为Entry链表的死循环而被hang住。也似乎有可能,活着的线程和死循环的线程同时执行在while里后,两个线程都能活着出去。所以,可能两个线程平安退出,可能一个线程hang在transfer中,可能两个线程都被hang住而又不一定在一个地方。

4)我反复的测试,出现一个线程被hang住的情况最多,都是e=e.next.next造成的,这主要就是例子put两份增量数据造成的。我如果去掉@标记3的输出,有时也能复现两个线程都被hang住的情况,但加上后就很难复现出来。我又把put的数据改了下,比如让两个线程put范围不同的数据,就能复现出e=e.next,两个线程都被hang住的情况。

上面罗哩罗嗦了很多,一开始我简单的分析后觉得似乎明白了怎么回事,可现在仔细琢磨后似乎又不明白了许多。有一个细节是,每次死循环的key的大小也是有据可循的,我就不打哈了。感觉,如果样本多些,可能出现问题的原因点会很多,也会更复杂,我姑且不再蛋疼下去。至于有人提到ConcurrentHashMap也有这个问题,我觉得不大可能,因为它的put操作是加锁的,如果有这个问题就不叫线程安全的Map了。

 

分析多线程并发写HashMap线程被hang住的原因

在blogjava上看到一文谁能帮忙解释一下为什么这个程序会死锁?,激发了我那能害死猫的好奇,所以很费劲的琢磨了这个问题。由于涉及的内容较多,就单独发文阐述一下。 public class Test...
  • wawmg
  • wawmg
  • 2014年02月18日 22:46
  • 3254

系统HANG住分析工具及方法

如果系统HANG住了,这个时候做一个SYSTEM STATE DUMP,对于分析HANG的原因十分重要。但是很多情况下,系统HANG住了就无法登录,那么如何进行分析呢? METALINK D...
  • rgb_rgb
  • rgb_rgb
  • 2014年04月18日 14:52
  • 2153

oracle关闭数据库被hang住的解决办法

这几天关闭oracle数据库,总是关不掉,一直挂在那儿,查看日志信息如下: Active call for process 20457 user 'oracle' program 'oracle@SY...
  • MisshqZzz
  • MisshqZzz
  • 2016年07月13日 12:46
  • 711

mysql连接hang住问题分析

【问题现象】: 1.       Linuxc多线程连接mysql数据库,每次都是短连接,操作完后就释放连接,有时候会出现mysql_real_connect挂住的现象 2.       挂住超时...
  • redsuntim
  • redsuntim
  • 2013年05月06日 19:46
  • 2431

分析多线程并发写HashMap线程被han…

原文地址:http://www.udpwork.com/item/2321.html   在blogjava上看到一文谁能帮忙解释一下为什么这个程序会死锁?,激发了我那能害死猫的好奇,所以很费劲的琢磨...
  • u012886792
  • u012886792
  • 2013年11月20日 11:58
  • 432

DB2数据库HANG住的时候应该收集什么数据以及如何处理

问题: DB2在使用过程中经常会遇到HANG死,本文旨在探讨如果整个库/DB2都HANG死的时候,应该收什么数据,如何处理,以便事后分析HANG住的原因。 一 数据收集 1.) 最简单的命令是db...
  • qingsong3333
  • qingsong3333
  • 2016年09月11日 23:18
  • 801

Python主进程hang住的两个原因

最近使用Python遇到两个非常不好定位的问题,表现都是Python主进程hang住。最终定位出一个是subprocess模块的问题,一个是threading.Timer线程的问题。subproces...
  • feilengcui008
  • feilengcui008
  • 2016年10月16日 17:08
  • 823

MySQL所有操作hang住问题的故障排查

MySQL-5.5.24社区版,无法正常连接数据库,发生hang住的情况,同时对已经存在的连接,执行所有操作均发生hang住的情况。...
  • slwang001
  • slwang001
  • 2017年03月21日 15:54
  • 612

一次linux的groupadd hang住处理

         某日从同事那里要了一台linux服务器,准备来安装timesten。在执行groupadd ttadmin时,命令一直没有响应,卡住了  使用top命令查看系统资源,发现系统处于...
  • Augusdi
  • Augusdi
  • 2015年04月05日 19:50
  • 994

Oracle数据库里什么情况下select操作会hang住

我们都知道在Oracle数据库里是“读不阻塞写,写不阻塞读”,那么我们可不可以认为在正常情况下,select操作是怎样都能执行,始终不会被hang住的呢?注意我这里提到的是正常情况下,不包括那些由于l...
  • liaoyuanzi
  • liaoyuanzi
  • 2012年06月29日 16:45
  • 2040
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:分析多线程并发写HashMap线程被hang住的原因
举报原因:
原因补充:

(最多只允许输入30个字)