hashMap死锁分析

概述

在hashMap中,当插入的值到达一定的量的时候,hashMap就会进行rehash,进行扩容,那么在扩容的时候就会发生死锁。

扩容条件

   public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

复制代码

以上为新增元素的put方法,如果新增的元素通过hash计算的索引,在table数组中不存在,那么就会调用addEntry方法

    void addEntry(int hash, K key, V value, int bucketIndex) {
        if ((size >= threshold) && (null != table[bucketIndex])) {
            resize(2 * table.length);
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }

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

复制代码

如果size大于threshold的值(为初始化的值),则会进行扩容,扩容的大小必须是2的倍数,因为在进行计算索引的时候,会进行位运算

    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);
    }

复制代码

根据newCapacity创建新的Entry,并做为参数传送给transfer方法,transfer方法,就是把原有table中entry的数据重新进行hash计算,并保存至newTable中,然后table=newTable,完成扩容,那么发送死锁的地方就发送在transfer方法中

   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;
            }
        }
    }
复制代码

通过前面的介绍,终于到了关键时刻了,下面看看怎么发送的死锁。

假如:table.length=2,值为3,7,5,排列如下

image

扩容后:table.length=4,排列如下

image

但是这个以上排列只是不发送死锁的情况,如果在高并发下场景,会出现不一样的结果。

 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;
            }
        }
    }
复制代码

假如: 有线程A、线程B

Entry<K,V> next = e.next;
复制代码

当B第一次执行完以下代码,进行了挂起,那么:e=3,next=7,在B挂起的时,线程A完成了transfer的扩容,那么7.next=3,3.next=null,扩容后如下图:

image

接着线程B继续执行,计算e=3的index为3,完成第一次循环:

image

接着进行第二次循环,此时:e=7,next=3

为啥next=3呢?因为在线程A完成扩容后,7的next为3,那么在执行到 Entry<K,V> next = e.next时,所以next=3

image

接着进行第三次循环,此时:e=3,next=null,而当执行到
==e.next = newTable[i]== 语句时,坑爹的事情发生了,形成闭环了,如下图:

image

注意此时还没有发送死锁,死锁是在获取元素时产生的,如调用一个不存在的元素,并且index是3,那么恭喜你死锁发生了。

猜你喜欢

转载自juejin.im/post/5ef9413c5188252e7a1c5499
今日推荐