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節點的一個指針, 這個是始料未及的, 也是出現死循環的關鍵.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章