HashMap链表插入方式为何从头插改成尾插
HashMap是我们最常用的一个类,我们也知道这个类是非线程安全的,尤其是在 Java1.7 中,还有可能出现环,造成死循环,通过这篇文章,你可以了解到为什么会出现这种问题。
Java7 的 HashMap 原理是 数组 + 链表,如果 hash 一致,就头插到链表中,头插就是导致成环的关键原因。Java8就改成了尾插,保持相对位置一致。
源码解析
下面先贴出涉及的方法,一些会在注释上解释。
你可以快速在这个网站上查找 Java1.7 HashMap 的源码http://hg.openjdk.java.net/jdk7/jdk7/jdk/file/e947a98ea3c1/src/share/classes/java/util/HashMap.java, 而不必去下载源代码。
1 | // 当需要扩容时,会执行此方法,参数是新的容量 |
举个栗子
举个在多线程情况下会产生环的例子:
- 图一展示了初始状态,容量为2,现在有 a、b 两个元素。(请忽略怎么没扩容这样的细节)
- 现在有两个线程同时要添加一个,且都会引发扩容。
- 线程1先执行,在执行完代码块①后停下来,让线程2去执行。此时,线程1看到的状态是如图二所示的,此时还没开始重新hash迁移数据。
- 线程2执行,且执行完了所有的步骤,那么在线程2中的状态如下:(假设a、b新hash出来的下标是3)
- 第一次循环: e=a, next=e.next=b, i=2(假设), newTable[2]=a, e=b
- 第二次循环: e=b, next=b.next=null, i=2, newTable[2]=b, e=null
- 循环结束
- 接下来又是线程1,从代码块①之后开始执行,状态如下:
- 第一次循环: e=a, next=e.next=b, i=2(假设), newTable[2]=a, e=b
- 第二次循环: e=b, next=b.next=a, i=2, newTable[2]=b, e=a ,这里出现了错误,由于线程2导致 b.next = a,正确情况应该是 null,导致了第三次循环
- 第三此循环: e=a, next=a.next=null, i=2, e.next=newTable2 ,在执行代码块②时产生环, newTable[2]=e=a ,又把头换成a了
- 循环结束