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

2014-11-24 08:14:45 · 作者: · 浏览: 2
文中提到的问题程序如下:

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):
www.2cto.com
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 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 e = table[bucketIndex];
table[bucketIndex] = new Entry(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];
transfer(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 e = src[j];
if (e != null) {
src[j] = null;
int k=0;//@标记2
do {
final Entry 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.e