转载请声明出处!数组
在多线程环境下,使用HashMap进行put操做会引发死循环,致使CPU利用率接近100%,HashMap在并发执行put操做时会引发死循环,是由于多线程会致使HashMap的Entry链表安全
造成环形数据结构,一旦造成环形数据结构,Entry的next节点永远不为空,就会产生死循环获取Entry。那么这个死循环是如何生成的呢?咱们来仔细分析下。数据结构
引起死循环,是在HashMap的扩容操做中,正常的扩容操做是这个流程。HashMap的扩容在put操做中会触发扩容,主要是三个方法:多线程
综合来讲,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。
你的赞和关注是我继续创做的动力~