java7的HashMap造成死循环的通俗化讲解

java7的HashMap造成死循环的通俗化讲解

在JDK1.7及以前的版本,如果在并发环境中使用HashMap保存数据,有可能会产生死循环的问题。产生这个问题是因为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];   
        //多线程情况下,上面创建好新的数组,死循环就是在下面方法中产生的
        transfer(newTable, initHashSeedAsNeeded(newCapacity));
        table = newTable;                                    
        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;                    
                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;
            }
        }
    }

其中最重要的就是进行transfer方法的部分,此部分详细说明了头插法的步骤和变量赋值变化:

 void transfer(Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;
        //遍历原HashMap数组,e是数组元素,若有链表衔接,就表示链表中的元素
        for (Entry<K,V> e : table) {
            while(null != e) {
                //这里就是将表示将e现在指代的元素的下一个元素赋给next这个变量
                Entry<K,V> next = e.next;                    
                if (rehash) {
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                int i = indexFor(e.hash, newCapacity);
                e.next = newTable[i];
                //将e指代的元素放入新表中根据hash(key)计算过的位置
                newTable[i] = e;
                //e这时就会变成 Entry<K,V> next = e.next; 这里赋值的next的变量,也就是将上面的next                  赋值给e
                e = next;
            }
        }
    }

单线程下是不会造成死循环的,只有在多线程下由于头插法的方式,就会造成死循环

死循环过程:

在这里插入图片描述

  • 1.假设原数组只有的大小只有2,这时有key(3),key(7),key(5)按照头插法插入了index:1形成链表,顺序如图,此时e指向a,next指向b,即:

    e=key(3);
    next=key(7);
    
  • 2.线程1执行完扩容后在 Entry<K,V> next = e.next;被挂起,紧接着线程2完成执行扩容的存放,到达如上状态,由于key(3),key(7)的rehash相同,所以他两个又被放入同一个index:3

  • 3.此时线程1重新回来,继续执行,这时 e=key(3);
    next=key(7); 执行完循环中的头插法语句newTable[i] = e; e这时是key(3)后:线程1的index:3中的第一个头插入元素就是key(3)

    • key3、5、7在内存中只有一份实例在堆中,方法栈中只是存有这些实例的引用,而且两个线程的rehash(key)是一样的,所以这时e所指向的key(3)和next指向的key(7)是和线程2中的指向是一样的

    在这里插入图片描述

  • 4.接着执行e = next;next此时是key(7),则:e=key(7);

在这里插入图片描述

e=key(7);
next=key(7);
  • 5.进入第二次循环,执行**Entry<K,V> next = e.next;**这时:按照reahash后的链表顺序(引用顺序)key(7)–>key(3)
e=key(7);
next=key(3);

在这里插入图片描述

  • 6.然后顺序执行接下来的**newTable[i] = e;**进行头插法,在线程2中将e=key(7)头插入key(3)之前,形成链表

在这里插入图片描述

  • 7.接着执行e = next;next此时是key(3),则:e=key(3);

在这里插入图片描述

e=key(3);
next=key(3);
  • 8.接着进入第三次循环,这一次就会造成循环指向,进入死循环。执行**Entry<K,V> next = e.next;**这时的e=key(3),按照链表顺序,e.next=null也即是next=null

在这里插入图片描述

  • 9.接着进行头插法,**newTable[i] = e;**进行头插法,在线程2中将e=key(3)头插入key(7)之前,同时key(7)又会指向key(3)这样也就造成了循环指向,进入死循环

在这里插入图片描述

为甚么java8可以避免这个情况:

  • 造成死循环的原因就是在扩容的时候采用头插法,改变了原本链表的顺序
  • java8中采用的是尾插法,不会改变原本的顺序,也就会形成死循环
  • 当然,如果要进行多线程编程,还是推荐使用ConcurrentHashMap
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值