關於JDK7HashMap多線程場景下擴容死循環問題的原因以及JDK8的解決方案

JDK7中HashMap擴容出現死循環的本質原因是,這個集合不是線程安全的,共享變量Node結點訪問出錯,當前線程在擴容到槽中最後一個節點B時,由於其他線程將最後一個節點B的next引用置成了A(下圖),被當前線程看到了(實際情況是,沒有同步措施的情況下,當前線程可能看到,可能看不到,和線程何時將自己本地內存的值刷新到主存有關,這裏涉及JMM模型和內存可見性,如有不懂請百度),導致了當前線程多執行了一次循環體,在循環體內將本不應該出現的結點A的next指向了槽點頭B,導致生成環。
其他線程擴容過程:
在這裏插入圖片描述
當前線程擴容過程:
在這裏插入圖片描述

//JDK7的擴容時 轉移結點的源碼    
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; //(正常的最後一次循環),B的next本爲null,但是其他線程修改成了A,被當前線程錯誤的觀察到了
                if (rehash) {
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                int i = indexFor(e.hash, newCapacity);
                e.next = newTable[i];   //(因爲錯誤的可見性導致的多餘的一次循環)將A的next再次指向槽點頭B,導致成環
                newTable[i] = e;
                e = next;               //(正常的最後一次循環)這將導致多進行一次循環
            }
        }
    }

JDK7由於在其他線程擴容時的搬運結點操作爲了提高效率採用的頭插法導致的當前線程不小心看到了錯誤的next指向,導致成環,JDK8採用尾插法(尾插法一是用於避免死循環,二是根據槽中個數判斷是否需要樹化)修復了此問題,但是由於是線程不安全的,還會有別的併發問題發生。

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