图解JDK7中的HashMap闭环和丢失问题

关于HashMap的线程安全问题,网上资料很多。对于1.7版本的闭环问题,看了很多网上的资料一直搞不懂,今天又琢磨了一下,终于明白了,用自己看得懂的方式记录下。

首先说下闭环产生的原因:1.7的HashMap在扩容复制时,采用的是头插入法,这会导致原数组中的链表反转,即将原来的正向,复制成反向链表。
而在多线程环境下,可能存在其他线程完成了扩容复制操作,完成了后的数组链表变成了原来的反向链表。
由于可见性的保证,当前线程继续复制时,复制的时其他线程已经完成了的反向链表,但当前线程又会将其反转,导致又变成了正向链表,那么在当前线程复制过程中会既有正向的链表又有反向的链表,从而可能产生闭环的情况。
具体的图例如下
左边的为扩容前的分布,右边的为正常单线程情况下完成复制后的情况。
图解JDK7中的HashMap闭环和丢失问题
首先说下头插入的过程,首先遍历旧数组链表,拿到第一个元素A,保存其后续链表B,然后计算在新数组位置1并拿到首节点NULL,将A的next设置为NULL
图解JDK7中的HashMap闭环和丢失问题
将A放入新数组对应位置中
图解JDK7中的HashMap闭环和丢失问题
将原后续链表续到原数组中
图解JDK7中的HashMap闭环和丢失问题
以上是正常的移动过程,现在分析并发场景下产生的闭环问题和丢失问题

闭环问题

  1. 线程1执行到这里后挂起
    图解JDK7中的HashMap闭环和丢失问题
  2. 线程2完成复制工作如下如
    图解JDK7中的HashMap闭环和丢失问题
  3. 线程1唤醒,继续操作,由于可见性,老数组中的B的next实际上已经变成了A,线程1此时会将A至于新数组索引1中,即虚线框会发送覆盖
    图解JDK7中的HashMap闭环和丢失问题
  4. 覆盖后变成下图
    图解JDK7中的HashMap闭环和丢失问题
  5. 继续完成B的复制,如下图
    图解JDK7中的HashMap闭环和丢失问题
  6. 旧数组中还剩下一个A,继续。还记得前面移动的步骤,首先会变更A的next指向B,然后放入新数组中。
    图解JDK7中的HashMap闭环和丢失问题
  7. 最终会成为下图形成闭环
    图解JDK7中的HashMap闭环和丢失问题

丢失

  1. 线程1在此处挂起
    图解JDK7中的HashMap闭环和丢失问题
  2. 线程2完成复制,如下图
    图解JDK7中的HashMap闭环和丢失问题
  3. 线程1苏醒,完成A的复制,此时B的next已经变成了NULL,C丢失
    图解JDK7中的HashMap闭环和丢失问题
  4. 复制B,C没了
    图解JDK7中的HashMap闭环和丢失问题

1.8尾插入发

由上面可知,丢失和闭环均是由于改变链表方向导致的,设计者一开始使用头插入法是考虑碰撞时后加入的数据访问的可能性更大,头插入有更好的查找性能。
1.8改为尾插入,不会进行链表方向的改变,因此不会发生上面两种情况