JDK1.7中HashMap为何会出现死循环问题的源码解读

JDK1.7中HashMap死循环问题源码解读。

JDK1.7中HashMap为什么会出现死循环呢,这里我看了网上很多资料,讲的很详细,但还是准备自己再重推一下,记下笔记,以便有更深的映像

put(K key, V value)逻辑

HashMap的死循环发生在扩容方法 resize(int newCapacity) 中,每当我们往map中添加元素时,HashMap会做一次校验,如果当前HashMap中链表的长度 size 已经大于等于当前的阈值 threshold 时,便会调用 resize(int newCapacity) 方法进行扩容。

  /**
   * 往bucket桶中添加元素
   */
  void addEntry(int hash, K key, V value, int bucketIndex) {
        if ((size >= threshold) && (null != table[bucketIndex])) { //长度校验
            //扩容 此时会传入当前table数组两倍的数值当作扩容后table的长度
            resize(2 * table.length);
            hash = (null != key) ? hash(key) : 0;
            //计算下标
            bucketIndex = indexFor(hash, table.length);
        }

        createEntry(hash, key, value, bucketIndex);
    }

resize 主要分为两步:创建新数组和将老数组中元素转移到新数组。在HashMap中,元素的下标都是通过 keyhash值 和 当前table长度 size 进行按位与计算取模得出的,而我们为HashMap进行扩容也就意味之我们需要重新为这些元素计算下标

我们继续进入 resize 方法中

    void resize(int newCapacity) {

        ...

        //用传入的参数new一个table数组
        Entry[] newTable = new Entry[newCapacity];
        //将老数组中的元素转移到新数组中,元素下标的重新计算也在这里执行
        transfer(newTable, initHashSeedAsNeeded(newCapacity));
        //将table指向扩容后的数组对象
        table = newTable;
        //重新计算阈值
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    }

transfer 方法

    void transfer(Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;
        //使用嵌套循环遍历数组 + 链表 结构中的每一个元素
        for (Entry e : table) {
            while(null != e) {
                //因为在之后的操作中会覆盖当前元素 e 的 next 属性
                //所以这里优先声明一个next变量,用于保存元素 e 的下一个节点
                Entry next = e.next;
                if (rehash) {
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                //重新计算出元素 e 在新数组的下标 i
                int i = indexFor(e.hash, newCapacity);
                //使用头插法将元素插入到新数组下位为 i 的链表当中
                e.next = newTable[i];
                newTable[i] = e;
                //将之前保存的下一节点 next 赋值给 变量 e,如果不为 null 
                //则再重复相同的逻辑转移该节点
                e = next;
            }
        }
    }

起初不能很好的理解transfer方法中的逻辑的话,可以尝试画张图增强理解。

死循环问题

在多线程高并发的环境下,有可能出现有多个线程同时调用扩容方法。这里假设有两个线程 线程A线程B 同时调用的扩容方法,并各自都创建了一个newTable

JDK1.7中HashMap为何会出现死循环问题的源码解读_第1张图片
hashMap01.jpg

此时线程A在执行到 Entry next = e.next; 这一步时,cpu时间片用户,线程B开始执行
此时线程A 中变量引用关系:

JDK1.7中HashMap为何会出现死循环问题的源码解读_第2张图片
HashMap02.png

cpu调度,线程B开始执行,在插入链表后,将newTable 赋值给 table属性之前,用完时间片
JDK1.7中HashMap为何会出现死循环问题的源码解读_第3张图片
HashMap03.png

线程A重新开始执行,在第一次while循环过后,线程A中的变量引用关系将会变成如下图所示

JDK1.7中HashMap为何会出现死循环问题的源码解读_第4张图片
HashMap04.png

第二次循环后
JDK1.7中HashMap为何会出现死循环问题的源码解读_第5张图片
HashMap05.png

然后到了关键的第三次循环,当代码执行到 e.next = newTable[i]; 时,我们发现 a节点b节点next 属性互相引用,形成了环。
JDK1.7中HashMap为何会出现死循环问题的源码解读_第6张图片
HashMap06.png

而此时如果运行 get 方法,去查找该链表的话,就会进入死循环。


迷迷糊糊得做了几年CRUD工程师,期间人也比较懒散,对学习不怎么上心,过着得过且过的日子。回过头来发现自己已远远落后于他人,现在在焦虑中重新拿起书本开始学习。虽然意识到问题有点迟了,但好在还不算晚。

你可能感兴趣的:(JDK1.7中HashMap为何会出现死循环问题的源码解读)