HashMap死循环问题
JDK1.7或者更老的版本中在多线程情况下是会存在死循环问题,究其原因是put过程中的resize方法在调用transfer方法的时候导致的死锁。这次我们来看看到底是哪里出了问题!
核心源码
在JDK8中,内部已经调整,解决了死循环问题,是如何解决的呢?将JDK7中头插入法改为末端插入。就是这么简单。关于这个,可以查看jdk8源码中的resize
方法。
上面提到是由于put
时出现问题,那么先来到put()
中看看:
我们看到,put一个不存在的新元素,必然增加一个节点,我们进入这个增加节点的方法:
检查是否需要扩容,需要的话就resize
:
下面就是对链表数据进行迁移:
核心的代码就是这么多,首先要强调一下:两个线程进来,是分别建立了两个独立的扩容后的数组,比如这里是两个长度为4的数组。老的数组为2个数就是唯一的。所以在第一步,线程2运行结束时,老的数组元素已经空了。
下面先演示一下正常的rehash
过程。
正常情况
- 假设了我们的
hash
算法就是简单的用 key mod 一下数组(hash表)的大小 - 最上面的是
old hash
表,其中的Hash
表的size=2, 所以key = 3, 7, 5,在mod 2以后都冲突在table[1]
这里了。 - 接下来的三个步骤是
Hash
表resize
成4,然后所有的<key,value>
重新rehash
的过程
注意到,在JDK7中,是按照头插入法依次插入的。所以7插到了3前面。
并发情况
1.初始情况
假设我们有两个线程。我用红色和浅蓝色标注了一下。
对于第一个线程先执行完这一行,然后挂起,此时 e
和 next
都附好值了:
而让线程二执行完成。于是我们有下面的这个样子:
因为Thread1
的 e
指向了 key(3)
,而 next
指向了 key(7)
,其在 Thread2
rehash
后,指向了 Thread2
重组后的链表。
2.Thread1被调度回来执行
- 先是执行
newTalbe[i] = e
:此时线程1的第三个位置就是指向元素3; - 然后是
e = next
,导致了e
指向了key(7)
; - 而下一次循环的
next = e.next
导致了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)
, 环形链表就这样出现了。
自己的简单整理
这里还是比较绕的,理解的最好方式左边放源码,右边放图,中间用草稿纸画一画。
那么,这里我在对其过程尽可能地讲明白一点。我们先确定7和3会全部落到扩容后的下标为3的位置(3%4=3,7%4=3)。
规定线程1开辟的数组为 arr1
,线程2开辟的数组为 arr2
;
1. 初始状态
- 线程一: e -> key3 , next -> key7
- 线程二: 数组3号位置 arr2[3] -> key7 -> key3
注意此时 key7
指向 key3
.
我们要明确一下,发生死循环,是指在put
操作完毕之后,最终生成的数组中有死循环引用才行,千万不要一开始看线程一种key3指向key7,然后线程二种key7指向key3就是死循环了。。。
2. 线程一继续执行
- i = 3
- e.next = key7,此时 e=key3 ,所以是 key3.next = key7(这是线程1的初始状态决定的)
- arr1[3] 指向 key3
- e 为 key7
3.由于e不为空,所以还会循环:
- 上一步 e 为 key7,所以 next = key7.next ,到线程2中一看是 key3 ,所以 next = key3(线程2中key7.next就是key3)
- i = 3
- e.next = key3------注意,这里就是Key7指向了key3,key7的next引用下面没有变过,所以这里做一下记录,即key7指向key3
- newTable[3] = key7
- e = key3
4.由于e不为空,所以还会循环:
- 上一步 e=key3 , next=null
- i=3
- key3.next = key7,注意,由于key7已经指向了key3,此时key3又指向key7,发生死循环
- newTable[3] = key3
- e = null
5.e为null,跳出循环。
此时发现key3又指向了key7。发生死循环。
整理自:https://coolshell.cn/articles/9606.html/comment-page-2#comments