1.概述
HashMap1.7当中,扩容的时候,采用的是头插法转移结点,在多线程并发的情况下会造成链表死循环的问题。
2.图解
假设有两个线程,线程1和线程2,两个线程进行hashMap的put操作,触发了扩容。
下面是扩容的时候结点转移的关键代码
void transfer(Entry[] newTable) {
Entry[] src = table;
int newCapacity = newTable.length;
for (int j = 0; j < src.length; j++) {
Entry<K,V> e = src[j];
if (e != null) {
//两个线程都先进入if
src[j] = null;
do {
Entry<K,V> next = e.next;
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i]; //线程1 这里还没执行 停下
newTable[i] = e;
e = next;
} while (e != null);
}
}
}
线程1和线程2 都进入if,然后线程1没有拿到cpu的资源在上面代码注释的地方停下了。此时的变量指针如下图所示:
记住 线程1中 E变量指向a结点,next变量指向b结点。
下面是线程2 拿到cpu的资源,执行结点转移
线程2停下,轮到线程1
因为之前线程1中E变量指向的是a结点,next变量指向的是b结点,所以如下图所示:
再来看看 刚才线程是在e.next = newTable[i] 这句代码还没执行的时候停下的,那么现在就要执行这一句代码
void transfer(Entry[] newTable) {
Entry[] src = table;
int newCapacity = newTable.length;
for (int j = 0; j < src.length; j++) {
Entry<K,V> e = src[j];
if (e != null) {
//两个线程都先进入if
src[j] = null;
do {
Entry<K,V> next = e.next;
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i]; //线程1刚才在这里停下,所以现在从这一句代码开始执行
newTable[i] = e;
e = next;
} while (e != null);
}
}
}
此时线程1 执行代码之后,就造成了链表的死循环,结果如下: