JDK7中HashMap链表成环的原因和解决方案

jdk1.7 HashMap扩容转移链表时形成环状链表的原因

大概就是:有a->b->null链表
		有两个线程,A和B
		A处理到a->null的时候,next指向b,接着B开始处理,并已经处理完毕,即b->a->null
		然后A又继续执行,而next指向b,所以变为b->a->null
		然后A的下一次循环的时候因为B的b->a->null,所以next = b.next,也就是a,所以a又指向b
		最后就变成a->b,而又b->a这样的循环

JDK8 中解决方案(从这里查看的
JDK 8 中扩容时,已经没有 JDK7 中的 transfer 方法了,而是自己重新写了扩容方法,叫做 resize,链表从老数组拷贝到新数组时的代码如下:

					//规避了8版本以下的成环问题
                    else { // preserve order
                        // loHead 表示老值,老值的意思是扩容后,该链表中计算出索引位置不变的元素
                        // hiHead 表示新值,新值的意思是扩容后,计算出索引位置发生变化的元素
                        // 举个例子,数组大小是 8 ,在数组索引位置是 1 的地方挂着一个链表,链表有两个值,两个值的 hashcode 分别是是9和33。
                        // 当数组发生扩容时,新数组的大小是 16,此时 hashcode 是 33 的值计算出来的数组索引位置仍然是 1,我们称为老值
                        // hashcode 是 9 的值计算出来的数组索引位置是 9,就发生了变化,我们称为新值。
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        // java 7 是在 while 循环里面,单个计算好数组索引位置后,单个的插入数组中,在多线程情况下,会有成环问题
                        // java 8 是等链表整个 while 循环结束后,才给数组赋值,所以多线程情况下,也不会成环
                        do {
                            next = e.next;
                            // (e.hash & oldCap) == 0 表示老值链表
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            // (e.hash & oldCap) == 0 表示新值链表
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        // 老值链表赋值给原来的数组索引位置
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        // 新值链表赋值到新的数组索引位置
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }

解决办法其实代码中的注释已经说的很清楚了,我们总结一下:

  1. JDK8 是等链表整个 while 循环结束后,才给数组赋值,此时使用局部变量 loHead 和 hiHead 来保存链表的值,因为是局部变量,所以多线程的情况下,肯定是没有问题的。

  2. 为什么有 loHead 和 hiHead 两个新老值来保存链表呢,主要是因为扩容后,链表中的元素的索引位置是可能发生变化的,代码注释中举了一个例子:
    数组大小是 8 ,在数组索引位置是 1 的地方挂着一个链表,链表有两个值,两个值的 hashcode 分别是是 9 和 33。当数组发生扩容时,新数组的大小是 16,此时 hashcode 是 33 的值计算出来的数组索引位置仍然是 1,我们称为老值(loHead),而 hashcode 是 9 的值计算出来的数组索引位置却是 9,不是 1 了,索引位置就发生了变化,我们称为新值(hiHead)。
    大家可以仔细看一下这几行代码,非常巧妙。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值