1-7 中 HashMap 死循环分析,透彻解析
现在 hashmap 中有三个元素,Hash 表的 size=2, 所以 key = 3, 7, 5,在 mod 2 以后都冲突在 table[1]这里了。

按照方法中的代码

对 table[1]中的链表来说,进入 while 循环,此时 e=key(3),那么 next=key(7),经过计算重新定位 e=key(3)在新表中的位置,并把 e=key(3)挂在 newTable[3]的位置


这样循环下去,将 table[1]中的链表循环完成后,于是 HashMap 就完成了扩容


并发下的扩容
上面都是单线程下的扩容,当多线程进行扩容时,会是什么样子呢?
初始的 HashMap 还是:

我们现在假设有两个线程并发操作,都进入了扩容操作, 我们以颜色进行区分两个线程。

回顾我们的扩容代码,我们假设,线程 1 执行到 Entry<K,V> next = e.next;时被操作系统调度挂起了,而线程 2 执行完成了扩容操作

于是,在线程 1,2 看来,就应该是这个样子

接下来,线程 1 被调度回来执行:
1)

2)

3)

4)

5)

6)

7)

循环列表产生后,一旦线程 1 调用 get(11,15 之类的元素)时,就会进入一个死循环的情况,将 CPU 的消耗到 100%。
总结
HashMap 之所以在并发下的扩容造成死循环,是因为,多个线程并发进行时,因为一个线程先期完成了扩容,将原的链表重新散列到自己的表中,并且链表变成了倒序,后一个线程再扩容时,又进行自己的散列,再次将倒序链表变为正序链表。于是形成了一个环形链表,当表中不存在的元素时,造成死循环。
虽然在 JDK1.8 中,Java 的开发小组修正了这个问题,但是 HashMap 始终存在着其他的线程安全问题。所以在并发情况下,我们应该使用 HastTable 或者 ConcurrentHashMap 来代替 HashMap。
你的赞和关注是我继续创作的动力~
最后
由于细节内容实在太多了,为了不影响文章的观赏性,只截出了一部分知识点大致的介绍一下,每个小节点里面都有更细化的内容!

需要这份文档的朋友可以帮忙点个赞,点击下方神秘超链接,就可以免费获取到了,还有小编准备的一份 Java 进阶学习路线图(Xmind)以及来年金三银四必备的一份《Java 面试必备指南》
资料领取链接:Java进阶学习路线图(Xmind)+《Java面试必备指南》

评论