JDK1.7中HashMap采用头插法扩容时的产生的链表循环问题

在JDK1.7时,HashMap的结构主要为数据+链表,在扩容时采用头插法,在单线程时表现正常,但在多线程时存在着链表循环的问题,下面对该问题来进行一个分析。

HashMap的容量是有限的,当元素插入一个接近饱和的HashMap之时,则会触发HashMap的扩容机制,也就是Resize。

触发Resize有两个因素

  1. Capacity
  2. LoadFactor

即当HashMap.Size >= Capacity * LoadFactor时,便进行Resize操作

Resize的具体操作

  1. 扩容

    创建一个新的Entry空数组,长度是原数组的2倍。

  2. ReHash
    遍历原Entry数组,把所有的Entry重新Hash到新数组。为什么要重新Hash呢?因为长度扩大以后,Hash的规则也随之改变。

ReHash代码

// 将所有Entry从当前表转移到newTable中
void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length;
    for (Entry<K,V> e : table) { //遍历table[1,2,3,4,5]
        while(null != e) { //遍历table中的链表table[i]
            Entry<K,V> next = e.next; //A线程在跑,B线程没有跑
            if (rehash) {//如果是重新Hash,则需要重新计算hash值
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            int i = indexFor(e.hash, newCapacity);//定位Hash
            //元素连接到桶中,这里相当于单链表的插入,总是插入在最前面,指针指向他下面的一个元素
            e.next = newTable[i];
            //newTable[i]的值总是最新插入的值
            newTable[i] = e;
            //继续下一个元素
            e = next;
        }
    }
}

HashMap扩容产生循环问题

假设一个HashMap已经到了Resize的临界点。此时有两个线程A和B,在同一时刻对HashMap进行Put操作:
在这里插入图片描述
此时原有的HashMap将进行扩容操作

假如此时线程B遍历到Entry3对象,刚执行完红框里的这行代码,线程就被挂起。
于线程B来说 : e = Entry3 next =Entry2
这时候线程A畅通无阻地进行着Rehash,当ReHash完成后,结果如下(图中的e和next,代表线程B的两个引用):
在这里插入图片描述
此时线程A已经完成了扩容,而后恢复线程B,执行其自己的ReHash操作,此时线程B中依旧保存在之前的数据即

e = Entry3 ;next =Entry2
在这里插入图片描述
经过计算得出i=3,故Entry3放入Index=3的位置

// 将所有Entry从当前表转移到newTable中
void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length;
    for (Entry<K,V> e : table) { //遍历table[1,2,3,4,5]
        while(null != e) { //遍历table中的链表table[i]
            Entry<K,V> next = e.next; //e.next=Entry2
            if (rehash) {//如果是重新Hash,则需要重新计算hash值
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            //经过计算得出i=3,故Entry3放入Index=3的位置
			int i = indexFor(e.hash, newCapacity);//定位Hash
            //指向i=3的位置,此时为null,e.next=null
			e.next = newTable[i];
            //e=Entry3;则newTable[i]=Entry3
            newTable[i] = e;
            //继续下一个元素,next=Entry2,则e=Entry2
            e = next;
        }
    }
}

在这里插入图片描述
而后执行下面代码

// 将所有Entry从当前表转移到newTable中
void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length;
    for (Entry<K,V> e : table) { //遍历table[1,2,3,4,5]
        while(null != e) { //遍历table中的链表table[i]
            Entry<K,V> next = e.next; //由线程A中,此时Entry2 e.next指向Entry3,故next=Entry3
            if (rehash) {//如果是重新Hash,则需要重新计算hash值
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            //经过计算得出i=3,故Entry3放入Index=3的位置
			int i = indexFor(e.hash, newCapacity);//定位Hash
            //之前的操作使得newTable[i]=Entry3
			e.next = newTable[i];
            //e=Entry2;newTable[i]=Entry2
            newTable[i] = e;
            //继续下一个元素,next=Entry3,则e=Entry3
            e = next;
        }
    }
}

此时数据情况如下
在这里插入图片描述
继续执行循环

// 将所有Entry从当前表转移到newTable中
void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length;
    for (Entry<K,V> e : table) { //遍历table[1,2,3,4,5]
        while(null != e) { //e=Entry3
            Entry<K,V> next = e.next; //Entry3 e.next=null
            if (rehash) {//如果是重新Hash,则需要重新计算hash值
                e.hash = null == e.key ? 0 : hash(e.key);
            }
			int i = indexFor(e.hash, newCapacity);//定位Hash
            //newTable[i]=Entry2,e.next=null
			e.next = newTable[i];
            //e=Entry3; newTable[i] = Entry2
            newTable[i] = e;
            //next=null;e=null
            e = next;
        }
    }
}

最终形成循环结构
newTable[i] = Entry2
e = Entry3
Entry2.next = Entry3
Entry3.next = Entry2
在这里插入图片描述
此时,问题还没有直接产生。当调用Get查找一个不存在的Key,
而这个Key的Hash结果恰好等于3的时候,由于位置3带有环形链表,所以程序将会进入死循环!

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值