1.7中HashMap死循环分析

转载请声明出处!数组

在多线程环境下,使用HashMap进行put操做会引发死循环,致使CPU利用率接近100%,HashMap在并发执行put操做时会引发死循环,是由于多线程会致使HashMap的Entry链表安全

造成环形数据结构,一旦造成环形数据结构,Entry的next节点永远不为空,就会产生死循环获取Entry。那么这个死循环是如何生成的呢?咱们来仔细分析下。数据结构

HashMap扩容流程

原理

引起死循环,是在HashMap的扩容操做中,正常的扩容操做是这个流程。HashMap的扩容在put操做中会触发扩容,主要是三个方法:多线程

addEntry

resize

transfer

综合来讲,HashMap一次扩容的过程:并发

一、取当前table的2倍做为新table的大小操作系统

二、根据算出的新table的大小new出一个新的Entry数组来,名为newTable线程

三、轮询原table的每个位置,将每一个位置上链接的Entry,算出在新table上的位置,并以链表形式链接3d

四、原table上的全部Entry所有轮询完毕以后,意味着原table上面的全部Entry已经移到了新的table上,HashMap中的table指向newTablecdn

实例

如今hashmap中有三个元素,Hash表的size=2, 因此key = 3, 7, 5,在mod 2之后都冲突在table[1]这里了。blog

按照方法中的代码

对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。

你的赞和关注是我继续创做的动力~

相关文章
相关标签/搜索