HashMap 死循环
在多线程环境中,使用HashMap进行put操作时会引起死循环,因为在HashMap本来就不支持多线程使用,要并发就用ConcurrentHashMap。 HashMap扩容时会导致死循环是在JDK1.7中,由于扩容时的操作是使用头插法,在多线程的环境下可能产生循环链表,由此导致了死循环。在JDK1.8中改为使用尾插法,避免了该死循环的情况,暂无此问题。
源码分析:
在创建了新的数组之后调用transfer方法来完成元素的迁移操作,具体迁移逻辑如下:
/**
* Transfers all entries from current table to newTable.
*/
void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
for (Entry<K,V> e : table) {
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); //线程1等线程2执行结束后
//从此处开始执行
//此时e的key=3,e.next.key=7
//但是此时的e.next.next的key=3了
//(被线程2修改了)
e.next = newTable[i];
newTable[i] = e;
e = next;
}
}
}
假设HashMap初始化大小为4,插入个3节点,恰巧这3个节点都hash到同一个位置
并发下的ReHash
在多线程环境下有线程一和线程二,同时对该map进行扩容执行扩容。
transfer代码中的这个细节:
do {
Entry<K,V> next = e.next; // 假设线程一执行到这里就被调度挂起了
// 此时e=key(3),next=key(7)
int i = indexFor(e.hash,newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
} while(e != null);
线程二继续执行并完成扩容后,(注意:这里的执行完成是指do...while循环执行 完毕,且HashMap中成员变量table已更新为线程二持有的newTable。注意!!!线程一和线程二进去transfer()后,newTable实际上是两个)
当线程一被调度回来执行之后,因为线程一执行的e.next =newTable[i];将key(3)插入到3号位置,同时3.next=key(7)。此时e=key(3),next=key(7);
线程一此时操作的HashMap已经是线程二扩容后的table了,此时的链表的顺序被反转了。因为在线程一挂起时e指向了key(3),而next指向了key(7),下一轮while时e指向了扩容完成后的key(7),而next指向了key(3),此时实际的链表结构是:
线程二扩容前原始链表(原始链表):key(3).next=key(7);key(7).next=key(5);key(5).next=null;
线程二扩容后链表:key(7).next=key(3);key(3).next=null;
线程一扩容继续扩容时的原始链表(在第二轮遍历时的链表 == 线程二扩容后链表):key(7).next=key(3);key(3).next=null;
线程一扩容后链表:key(3).next=key(7);key(7).next=key(3);key(7).next=key(3);(红色是根据原始链表产生,绿色是根据线程二扩容后链表产生)
总结
HashMap之所以在并发下的扩容造成死循环,是因为,多个线程并发进行时,因为一个线程先期完成了扩容,将原Map的链表重新散列到自己的表中,并且链表变成了倒序,后一个线程再扩容时,又进行自己的散列,再次将倒序链表变为正序链表。于是形成了一个环形链表,当get表中不存在的元素时,造成死循环。
链表头插法:插入顺序和遍历顺序相反,先进后出,后进的节点需要知道前一个节点的指针地址
链表尾插法:插入顺序和遍历顺序相同,先进先出,前一个节点需要知道后进的节点的指针地址
原因:
JDK1.8之前的头插法扩容前和扩容后的链变中节点顺序的会变化(头节点变尾节点,尾节点变头节点,循环往复)
JDK1.8之后的尾插法扩容前和扩容后的链表中节点顺序不变(以前是头节点就一直是头节点)
文章参考:
https://zhuanlan.zhihu.com/p/67915754
https://www.iteye.com/blog/firezhfox-2241043
https://www.cnblogs.com/codingmengmeng/p/9941866.html
本文地址:https://blog.csdn.net/qq_27376871/article/details/109630451
上一篇: easyexcel导入Excel
下一篇: STM32 SWD 只能下载一次的问题