原帖地址:https://www.jianshu.com/p/4930801e23c8
进行put操作到阈值时,进行扩容的时候会导致死循环
void transfer(Entry[] newTable)
{
Entry[] src = table;
int newCapacity = newTable.length;
//从OldTable将元素一个个拿出来,然后放到NewTable中
for (int j = 0; j < src.length; j++) {
Entry<K,V> e = src[j];
if (e != null) {
src[j] = null;
do {
Entry<K,V> next = e.next;
//计算节点在新的Map中的位置
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
} while (e != null);
}
}
}
假设hash算法就是简单的用key mod Entry数组的长度

假设hash算法就是简单的用key mod Entry数组的长度。这里一定注意e和next的指向,当并发resize()时,这两个指针对于死锁产生起着至关重要的作用。根据方法执行情况,原Map中的链表元素在新的Map中将顺序颠倒,如上图所示,经过一次resize()后key为7的节点排在了key为3的节点之前。
do {
Entry<K,V> next = e.next;
//计算节点在新的Map中的位置
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
} while (e != null);
再次黏贴这段代码就是强调这个do while循环就是产生死锁的罪魁祸首。下面模拟死锁产生的过程。
注意,并非所有情况下都会产生死锁,这也需要线程之间的默契配合,怎么讲呢,如图所示:
①此时线程一,e指向key为3的节点,next指向key为7的节点。这点很重要,记下来。去执行线程二。
②假设线程二正常执行,结束后的状态如下:
③此时线程一被唤醒,线程一的工作空间里,e和next指向的元素依旧是key为3和7的节点。线程一开始执行。
④目前还没发生问题,线程一接着工作。把key(7)摘下来,放到newTable[i]的第一个,然后把e和next往下移。
e.next = newTable[i] 导致 key(3).next 指向了 key(7)。注意:此时的key(7).next 已经指向了key(3), 环形链表就这样出现了。
*转载者总结:
根本原因是JDK1.8之前的扩容会将结点倒序
其实也就是并发时,将旧数组的数据移到新数组对应位置时,该位置上不能有旧数组上的数据,否则就会形成环。
JDK1.8的解决(四个指针)
通过两个指针loHead/loTail指向重hash后位置不变的链表头和尾
以及hiHead/hiTail指向重hash后位置+oldCap的链表头和尾
来避免倒序的问题,从而解决死循环的问题。
但是HashMap还是有并发问题,所以还是要用ConcurrentHashMap