• Hashmap头插法死循环


      先来看一看老版本HashMap扩容代码:

    void resize(int newCapacity)
    {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        ......
        //创建一个新的Hash Table
        Entry[] newTable = new Entry[newCapacity];
        //将Old Hash Table上的数据迁移到New Hash Table上
        transfer(newTable);
        table = newTable;
        threshold = (int)(newCapacity * loadFactor);
    }

       其中,重点在于transfer():

    void transfer(Entry[] newTable)
    {
      //复制一个原数组src,Entry是一个静态内部类,有K,V,next三个成员变量 Entry[] src
    = table;
      //数组新容量
    int newCapacity = newTable.length;// 从OldTable里摘一个元素出来,然后放到NewTable中 for (int j = 0; j < src.length; j++) { Entry<K,V> e = src[j];//取出原数组一个元素 if (e != null) {//判断原数组该位置有元素 src[j] = null;//原数组位置置为空 do {//对原数组某一位置下的一串元素进行操作 Entry<K,V> next = e.next;//next是当前元素下一个 int i = indexFor(e.hash, newCapacity);//i是元素在新数组的位置 e.next = newTable[i];//此处体现了头插法,当前元素的下一个是新数组的头元素 newTable[i] = e;//将原数组元素加入新数组 e = next;//遍历到原数组某一位置下的一串元素的下一个
          } while (e != null);
        }
      }
    }

       接下来图示单线程情况下,do循环内的情况:

      初始:当前数组容量为2,有三个元素3、7、5,此处的hash算法是简化处理(对容量取模)。因此,3、7、5都在数组索引1对应的链表上。

      扩容新容量为2*2=4。

      第一步:当前Entry e对应3,next对应7,新位置i为3,然后将3插入新数组对应位置。

      第二步:当前Entry e对应7,next对应5,新位置i为3,然后将新数组对应索引处的元素3添加到7的尾巴后(头插),然后将7插入新数组对应位置。

      第三步:当前Entry e对应5,next对应null,新位置i为1, 然后将5插入新数组对应位置。

       接下来图示多线程情况下死循环场景:初始条件相同。

      如果有两个线程:

        线程一执行到 Entry<K,V> next = e.next; 便挂起了,即此时Entry e是3,next是7,3是在7前面的。

        线程二执行完成。

      此时如下图所示,线程一的3的next是7,而线程二的7的next是3。(此处是Entry里的next成员变量,在多个线程中相同Entry不冲突)。此时可以看出出现了死循环问题。

       如果此时线程一继续往下执行:

       第一步:当前Entry e对应3,next对应7,新位置i为3,然后将3插入新数组对应位置。

       第二步:当前Entry e对应7,next对应3(单线程情况下是5),新位置i为3,然后将7插入新数组对应位置。

       第三步:当前Entry e对应3,next对应7,此处死循环,永远不会跳出while循环。

     

    总结归纳:多线程情况下,使用头插法会导致链表节点之间的关系混乱,出现倒排现象,例如原本3->7->5变成7->3,其他线程此时再进行扩容是会出现死循环。 

     单线程

    0   

    1        3 ->7 ->5

              e     next

         e next

           e  next=null   

    0

    1    5

    2

    3     7  -> 3    

    0   

    1        3 ->7 ->5

              e     next  线程池一中断

    线程二执行完

    0

    1    5

    2

    3     7  -> 3    

    线程一继续

        出现死循环问题

  • 相关阅读:
    Android Jetpack之WorkManager: 观察结果
    解决'androidx.arch.core:core-runtime' has different version for the compile (2.0.0) and runtime (2.0.1)
    我要研究一下minio,管理大量的照片
    分发消息的写法
    百度地图坐标转换
    HighChart 实现从后台取数据来实时更新柱状和折线组图
    导出Excel
    Java 8新特性之集合
    java中的Switch case语句
    提问:"~"运算符
  • 原文地址:https://www.cnblogs.com/qmillet/p/13054208.html
Copyright © 2020-2023  润新知