都说hashmap是线程不安全的,多线程时候容易造成死锁
死锁的原因就在扩容的时候
原hashmap结构图假如如下,A、B是hashmap其中两个Entry,在扩容之前具有相同的index,形成链表结构,如图

1、线程一运行到Entry A, B = A.next,运行完这行后。线程一进入线程等待(此时链表关系A.next = B)

2、线程二正常运行,顺利完成扩容,在偶尔情况下恰好扩容之后A、B,在newTable中还是拥有相同的index,但此时由于经过了一次扩容,由于扩容过程也是头插法,所以原来在链表后的Entry变更到链表前边,链表翻转过来了。在newTable中为B.next=A,线程二结束。扩容完成,hashmap结构如下图(A,B的index都为4)

3、此时线程一唤醒,线程一还是A.next=B,继续执行(我们这里既然讲的特殊,特殊化为扩容后A,B在newTable的索引任然相同)
e.next = newTable[i];
newTable[i] = e;
e = next;
4、线程一继续执行,e.next=newTable[i](i=4)(这e就是A)
因为线程二扩容完成newTable[i](newTable[4])= Entry B(这个B是已经完成线程二扩容的hashmap中的newTable第4号元素链表)
5、线程一继续执行:newTable[i] = e;(将A,头插入到B之前)

6、实际上并不是出现两个A,而是出现了环链

7、线程一继续执行:e=next(next = B,是在线程一等待之前的变量值)
8、线程一继续执行: while(B!=null),满足条件,(此时A,B已经是环链结构了)
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;
}
9、线程一继续执行: Entry<K,V> next = e.next;
变量情况:next = A, e = B
10、线程一继续执行: e.next = newTable[4],(即时B.next = newTable[4], 可以从图上图中看出来newTable[4]=A )
相当于B.next = A
11、线程一继续执行: newTable[i] = e; (即是 赋值newTable[4] = B),将B头插在链表链首位置
结构变成

12、线程一继续执行:e = next ; (e又变成了A)
while将会一直循环下去,形成死循环,不停增大JVM开销,最后内存溢出。
本文详细剖析了HashMap在多线程环境下因扩容操作引发死锁的问题。线程一在扩容期间暂停,线程二完成扩容并改变链表结构,导致线程一恢复时形成环链,进而陷入死循环,消耗资源直至内存溢出。理解这一问题对于避免并发编程中的这类陷阱至关重要。
5661

被折叠的 条评论
为什么被折叠?



