清晰的理解了HashMap循环链表问题

今天讲的这个话题,是为了学习ConcurrentHashMap打个基础,后续会更新。

我们都知道在jdk1.7的时候,我们的HashMap是使用 数组(bucket)+链表 组成的结构,那么今天我们来看看HashMap在并发情况下,为什么会出现死链的问题。

分析

我们知道HashMap在容量达到设定的阈值时(0.75f)就会进行扩容,那么扩容就设计到迁移的问题,而1.7的HashMap在插入的时候是使用的头插法。

图片.png

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;//T1执行到这里时间片到期退出
            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;
        }
    }
}
复制代码

比如现在有两个线程T1和T2同时加入,如图的Entry1和Entry2,假设达到了阈值,这时两个线程同时进行扩容(这里会有两个newTable),T1.e和T1.next分别如图中所示,这时候时间片到期,让出CPU执行资源,而T2完成了resize,如图右边所示,这时T1线程醒过来,发现T1.e和T1.next图右所示。他们继续往下执行代码

e.next = newTable[i];
newTable[i] = e;
复制代码

好的,头插法,放到第二newTable,e.next=null,newTable[i]=e

图片.png

接着循环e = Entry2,next=Entry1。然后继续头插,变成下面这样。

图片.png 最后一次循环,e=Entry1,next=null。继续头插。

图片.png 这里就已经形成一个newTable环形链路了,当程序进行寻找key的时候,就有可能一直出不去,造成CPU资源利用率100%的情况。

总结

所以主要原因就是使用了头插法,造成在并发情况下,程序会死循环,永远找不到key(在循环链表的后面)。所以在1.8的HashMap就进行了改进,使用了尾插法,保证顺序的情况下同时不会造成死循环。毕竟是在多线程情况下,虽然不会造成死循环,但是会造成语义不正确的情况。

猜你喜欢

转载自juejin.im/post/7086372830807326733