HashMap扩容产生死链分析(JDK7)

 void transfer(Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;
        for (Entry<K,V> e : table) {
            while(null != e) {
                Entry<K,V> next = e.next;  // (1)
                if (rehash) {
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                int i = indexFor(e.hash, newCapacity); //(2)
                e.next = newTable[i]; //(3)
                newTable[i] = e; () //(4)
                e = next; //(5)
            }
        }
    }

扩容时transfer方法将原来数组元素复制到扩容后的数组。for循环遍历原来的数组,while循环是遍历链表,rehash这段代码先忽略不管,假设rehash = false(前提)。

分析while循环,假设被复制元素不存在链表,此时(1): Entry<K,V> next = null;(2):假设这里没有hash冲突;(3):e.next = null ,此时newTable[i]上是没元素的,(4):newTable[i] = e ;(5):e = null ; 循环结束;假设(2)存在hash冲突,也就是newTable[i]这里存在元素了(假设newTable[i] = N),那么(3):e.next = N,(4):newTable[i] = e;这里就把原来数组的元素放在了新数组链表的头部,然后把新数组已经存在的元素放新元素下面,(5):e = null ; 循环结束;

被复制元素存在链表分析。被复制链表a>b>c>null (假设链表复制后还在新数组上同一个链表,链表没有其他新元素);

复制a:(1): Entry<K,V> next = b;(2):这里没有hash冲突;(3):e.next = null ,此时newTable[i]上是没元素的,(4):newTable[i] = a;(5):e = b ;

复制b元素:(1): Entry<K,V> next = c;(2):这里hash冲突;(3):e.next = a,此时newTable[i]第一次遍历的元素a,(4):newTable[i] = b;(5):e = c ;复制c元素:

复制c元素:(1): Entry<K,V> next = null;(2):这里hash冲突;(3):e.next = b,此时newTable[i]第一次遍历的元素a,(4):newTable[i] = b;(5):e = null , 循环结束。新链表顺序为:c>b>a>null,与原链表顺序刚好相反。

transfer 方法在单线程下时没问题的,多线程下可能导致死链。假设现在有两线程T1和T2同时进入transfer 方法,同时抵达(1),此时T1继续往前走,T2在(1)处阻塞,T1跑完整个循环后,T2阻塞结束。那么此时T1新生成了c>b>a>null链表。T1和T2都会创建新的数组,数组内的局部变量为线程私有,如while循环里面的e,next。

T2开始它的复制过程:

复制a:(1): Entry<K,V> next = b;(2):这里没有hash冲突;(3):e.next = null ,此时newTable[i]上是没元素的,(4):newTable[i] = a;(5):e = b (注意这里a和a.next都是在T2自己的线程栈内);

复制b:(1): Entry<K,V> next = a,(2):这里没有hash冲突,(3):e.next = a,(4):newTable[i] = b;(5):e = a (这里为啥是next = a,因为T1已经修改了指针,新的顺序是c>b>a>null,而T2阻塞在(1)的时候,它只读取到e = a,和 e.next = b,而原始链表的c = b.next 只有while走第二次它才能读取到,由于阻塞它没读取机会,此后读取的就是T1改变后的顺序了);

while继续循环,(1): Entry<K,V> next =  null;(2):这里没有hash冲突,(3):e.next = b,(4):newTable[i] = a;(5):e = null;此时循环结束,新链表只有a和bl两个元素,T2扩容后顺序为b>a>b,形成死链,完成后续操作后替代原来的数组。HashMap执行get操作时,会遍历链表,然后陷入死循环。

 

static int indexFor(int h, int length) {
    // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
    return h & (length-1);
}

这段代码,如果rehash = false情况下,新下标要么维持不变,要么就是原来下标+原来数组大小;假设数组大小16,16-1 =15,二进制就是1111,h低4位xxxx,1111&xxxx = xxxx, 这是原来的下标;扩容后32,32-1 = 31,二进制11111; h 的低位是 x1xxxx或者x0xxxx;11111& x1xxxx = 1xxxx,1xxxx 对比xxxx,相当于原数组下标+原数组大小;11111& x0xxxx = 0xxxx, 0xxxx = xxxx,即新旧数组下标没变。

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值