都说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开销,最后内存溢出。