JDK1.7或者更老的版本中在多线程情况下是会存在死循环问题,究其原因是put过程中的resize方法在调用transfer方法的时候导致的死锁。这次我们来看看到底是哪里出了问题!

核心源码

在JDK8中,内部已经调整,解决了死循环问题,是如何解决的呢?将JDK7中头插入法改为末端插入。就是这么简单。关于这个,可以查看jdk8源码中的resize方法。

上面提到是由于put时出现问题,那么先来到put()中看看:

image

我们看到,put一个不存在的新元素,必然增加一个节点,我们进入这个增加节点的方法:

image

检查是否需要扩容,需要的话就resize:

image

下面就是对链表数据进行迁移:

image

核心的代码就是这么多,首先要强调一下:两个线程进来,是分别建立了两个独立的扩容后的数组,比如这里是两个长度为4的数组。老的数组为2个数就是唯一的。所以在第一步,线程2运行结束时,老的数组元素已经空了。

下面先演示一下正常的rehash过程。

正常情况

image

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

注意到,在JDK7中,是按照头插入法依次插入的。所以7插到了3前面。

并发情况

1.初始情况

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

对于第一个线程先执行完这一行,然后挂起,此时 enext 都附好值了:

image

而让线程二执行完成。于是我们有下面的这个样子:

image

因为Thread1e 指向了 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) ;

image

3.一切安好

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

image

4.环形链接出现

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

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

image

自己的简单整理

这里还是比较绕的,理解的最好方式左边放源码,右边放图,中间用草稿纸画一画。

那么,这里我在对其过程尽可能地讲明白一点。我们先确定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. 线程一继续执行

image

  • 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