HashMap并发场景分析

背景

看书过程中《Java并发编程的艺术》,在看这本书的过程中,作者提到,在并发编程中,使用HashMap可能导致程序死循环,那么作者说的到底对不对呢,为什么会这样呢,现在有没有进行改进呢,所以就进行查阅资料,进行学习,并总结成以下文章

HashMap并发中存在的问题

  1. 非线程安全

这个是众所周知的

  1. 扩容导致死循环

这个问题 的背景是JDK 1.7中,JDK1.8中hashMap源码进行了修改,应没有此问题,所以导致死循环问题,只在JDK7中存在

并发场景下会导致死循环分析

模拟一个场景,有原HashMap如下: 

HashMap并发中死循环分析_死循环

多线程场景下,有线程一和线程二,同时对map 进行扩容, 

HashMap并发中死循环分析_链表_02

当线程一执行到如下代码时被挂起 

HashMap并发中死循环分析_倒序_03

此时线程1的数据结构为,table已经扩容完毕,重新计算每个元素位置时被挂起,此时key为3 的还在1的位置, 3的next 为7 

HashMap并发中死循环分析_倒序_04

 这时,线程2完成扩容,扩容后的数据结构为 

HashMap并发中死循环分析_死循环_05

当线程1被调度回来执行之后,因为线程一执行的e.next = newTable[i]; 将key3插入到3号位置,同时3.next = key7, 此时e= key3, next = key7;

HashMap并发中死循环分析_倒序_06

HashMap并发中死循环分析_链表_07

 当执行到e=next的时候,e=key7,next=key7;

然后开始下一轮while。但此时因为线程二已经将key=7的next设置为key3(问题就在这里,线程二执行的时候,key7.next已经是null了,但这里线程一去执行的时候key7.next却是key3)。所以当第二轮循环开始,执行next=e.next后,next = key3。

HashMap并发中死循环分析_链表_08

 之后通过头插法,将key7插入key3之前。在执行完next=e.next 之后,e=key3,next=key3;

HashMap并发中死循环分析_倒序_09

然后开始第三轮循环。e和next都是key3。所以根据头插法。key3又要插入key7之前,这就导致了key3.next为key7,key7.next为key3

HashMap并发中死循环分析_倒序_10

 所以当我们去get值得时候,当定位到3的时候,就会产生死循环。导致永远拿不到数据。

JDK1.8存不存在死循环问题

应该不存在,JDK8中将链表扩容到达一定阈值后,转为红黑树, 没有相关的问题

总结

HashMap之所以在并发下的扩容造成死循环,是因为,多个线程并发进行时,因为一个线程先期完成了扩容,将原Map的链表重新散列到自己的表中,并且链表变成了倒序,后一个线程再扩容时,又进行自己的散列,再次将倒序链表变为正序链表。于是形成了一个环形链表,当get表中不存在的元素时,造成死循环。在1.8当中,链表扩容转为红黑树,没有相关的问题。