HashMap闭环(死循环)的详细原因

只有在多线程并发的情况下才会出现这种情况,那就是在put操作的时候,如果size>initialCapacity*loadFactor,hash表进行扩容,那么这时候HashMap就会进行rehash操作,随之HashMap的结构就会很大的变化。很有可能就是在两个线程在这个时候同时触发了rehash操作,产生了闭合的回路。

 

正常的扩容的过程

画了个图做了个演示。

  • 我假设了我们的hash算法就是简单的用key mod 一下表的大小(也就是数组的长度)。
  • 最上面的是old hash 表,其中的Hash表的size=2, 所以key = 3, 7, 5,在mod 2以后都冲突在table[1]这里了。
  • 接下来的三个步骤是Hash表 resize成4,然后所有的<key,value> 重新rehash的过程

       

 

 

并发下的扩容过程

1)假设我们有两个线程。我用红色和浅蓝色标注了一下。

我们再回头看一下我们的 transfer代码中的这个细节:

do { 
    Entry<K,V> next = e.next; // <--假设线程一执行到这里就被调度挂起了 
    int i = indexFor(e.hash, newCapacity); 
    e.next = newTable[i]; 
    newTable[i] = e; 
    e = next; 
} while (e != null); 

假设我们的线程一执行到Entry<K,V> next = e.next; (如上图代码第一步)就被调度挂起了,而我们的线程二执行完成了。于是我们有下面的这个样子。

 

注意,因为Thread1的 e 指向了key(3),而next指向了key(7),其在线程二rehash后,指向了线程二重组后的链表。我们可以看到链表的顺序被反转后。

2)线程一被调度回来执行。

  • 先是执行 newTalbe[i] = e;
  • 然后是e = next,导致了e指向了key(7),(这是因为线程1被挂起时e.next指向key(7))
  • 而下一次循环的next = e.next导致了next指向了key(3)(这是因为链表已经被Thread2扩容过了,可以参考Thread2扩容后图key(7)的next为key(3))

     

3)一切安好。

线程一接着工作。把key(7)摘下来,放到newTable[i]的第一个,然后把e和next往下移。

 

4)环形链接出现。

e.next = newTable[i] 导致  key(3).next 指向了 key(7)

注意:此时的key(7).next 已经指向了key(3), 环形链表就这样出现了。

 

于是,当我们的线程一调用到,HashTable.get(11)时,悲剧就出现了——Infinite Loop。

 

本质原因:经过分析和理解,本人认为导致出现循环的原因是线程一被挂起时是仍然保留原链表的当前元素e和下一个元素next,而线程二扩容完成以后由于采用头插法已经将当前元素e和下一个元素next位置进行互换,这样导致线程一在扩容过程中会将当前元素e进行两次处理。(第一次是线程被唤起时处理,第二次是采用线程二互换的关系重新定位到e),结果导致出现链表闭环。

 

转载:https://www.cnblogs.com/jing99/p/11319175.html

 

 

posted @ 2021-01-04 17:24  彼岸-花已开  阅读(276)  评论(0)    收藏  举报