JDK1.7的HashMap链表死循环分析

在JDK1.7及以前的版本,如果在并发环境中使用HashMap保存数据,有可能会产生死循环的问题,造成cpu的使用率飙升。产生这个问题是因为JDK1.7及以前的版本中,HashMap扩容采用的是头插入,1.8做的改进是采用尾插法,所以不会造成死循环的问题。
首先,来看1.7扩容的代码:

 //进行扩容时方法
    void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }

        Entry[] newTable = new Entry[newCapacity];              //---------111
        //多线程情况下,上面创建好新的数组,死循环就是在下面方法中产生的
        transfer(newTable, initHashSeedAsNeeded(newCapacity));  //---------222
        table = newTable;                                       //---------333
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    }
    
    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;                      //----------444
                if (rehash) {
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                int i = indexFor(e.hash, newCapacity);
                e.next = newTable[i];
                newTable[i] = e;
                e = next;
            }
        }
    }

一开始的数据结构如图:
在这里插入图片描述
假设两个线程,第一个线程执行到注释111处,cpu时间执行完了,轮到第二个线程执行,第二个线程执行完注释111处,进入到注释222的方法,执行到444处(这个地方很重要),这时变量e指向节点a,变量next指向节点b,第二个线程的cpu执行完了,这时候两个线程都创建了新的哈希表,如图;
在这里插入图片描述
又轮到第一个线程执行,假设a,b,c三个节点刚好映射到7这个位置,
先移动a节点,如图;
在这里插入图片描述
再移动节点b,如图;
在这里插入图片描述
最后移动节点c,如图;结合三张图片可以看出来,头插法,就是从链表的头部插入

在这里插入图片描述
这个时候线程1的时间片用完,也就是注释222的方法已经执行完毕,但是还没执行注释333,也就是内部的table还没有设置成新的newTable,这时候线程2开始执行,这时内部的引用关系如下:
在这里插入图片描述

再贴一下tranfer方法的代码,我们刚才说线程2执行到注释444处,这时变量e指向节点a,变量next指向节点b

 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;                      //----------444
                if (rehash) {
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                int i = indexFor(e.hash, newCapacity);
                e.next = newTable[i];
                newTable[i] = e;
                e = next;
            }
        }

继续执行,7会指向节点a;然后b成了变量e,
![(https://img-blog.csdnimg.cn/20190410221243642.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NDAyOTY5Mg==,size_16,color_FFFFFF,t_70)
因为e不是null,则继续执行循环体,7会指向节点b,b指向a,因为上一张可以看出节点本来就指向a,所以变化如图,此时a就变成了节点e。
在这里插入图片描述
下面就是链表成环的关键;
e.next = newTable[i]; newTable[i]也就是7的位置,指向的是节点b,所以会把节点a的next指向b,而从上面的图可以看到,节点b的next是指向节点a的,这样就构成了死循环。
newTable[i] = e;将7的位置指向的是节点a。此时结构如图:
在这里插入图片描述
另外,可以看出,如果线程2执行到了注释333时,把newTable设置成到内部的table,节点c的数据就会丢了。

之前自己也没懂为什么1.7hashmap为造成死循环,看了下面这篇文章才看懂,推荐一下。
https://www.jianshu.com/p/1e9cf0ac07f4

  • 5
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值