HashMap在 JDK1.7中的线程安全问题

HashMap在并发执行put操作时会引起死循环,是因为多线程会导致HashMap的Entry链表形成环形数据结构,一旦形成环形数据结构,Entry的next节点永远不为空,就会产生死循环获取Entry。

产生死循环的过程

(仅在jdk1.7有效, 1.8由于变成了尾插法, 虽然也有并发线程安全问题, 但是不会造成死循环了)

    /**对HashMap进行容量扩充
     * Transfers all entries from current table to newTable.
     */
    void transfer(Entry[] newTable) {
        Entry[] src = table;
        int newCapacity = newTable.length;
        for (int j = 0; j < src.length; j++) {//遍历原table中的所有表头
            Entry<K,V> e = src[j];
            if (e != null) {
                src[j] = null;
                do {//依次将链表中的元素,重新添加到新的table中
                    Entry<K,V> next = e.next;//          代码 1
                    int i = indexFor(e.hash, newCapacity);
                    e.next = newTable[i];
                    newTable[i] = e;
                    e = next;
                } while (e != null);
            }
        }
    }

首先,假设我们有两个并发的线程同时进行容量扩充,因此每个线程都拥有一个newTable。状态如下所示:
在这里插入图片描述
再假设,线程1和线程2都执行了代码1。状态如下所示:
在这里插入图片描述
接着,线程1执行完整个链表转移操作,线程2什么都不做。状态如下所示:
注:这里假定元素E 1和E 2重新映射后在newTable中的索引位置不变(或者说一致),由于是头插法,故元素逆序。
在这里插入图片描述
接着,线程2向newTable 2中插入一个元素。状态如下所示:
在这里插入图片描述
注意:特别注意e 2和next 2的指针位置。
因为e 2!=null,因此线程2继续转移元素。状态如下所示:
在这里插入图片描述
因为e 2!=null,因此线程2继续转移元素。状态如下所示:
在这里插入图片描述
发现没有,此状态与上上步的状态一样,因此接下就进入了死循环。

一句话总结就是:

两个线程同时并发扩容, 其中一个线程因为时间片用完, 只执行了第一步指针指向节点, 将e和next指针指向了原来的hashmap节点, 但是第二个完成了扩容的全过程, 因为1.7是头插法, 导致了节点颠倒, 所以导致了第一个线程的指针颠倒, 并且出现了一个next指针指向e节点的一个指针, 这个是始料未及的, 也是出现死循环的关键.

原创文章 280 获赞 464 访问量 10万+

猜你喜欢

转载自blog.csdn.net/qq_33709508/article/details/105525904