HashMap在併發環境下的死循環分析

      今天在看《分佈式java應用》這本書的時候看到作者提到HashMap在多線程併發的環境下有可能出現死循環,導致cpu100%的現象,看了下源碼結合網上的分析說明下這種可能性。可能出現問題的地方是在擴容的時候

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);
        table = newTable;
        threshold = (int)(newCapacity * loadFactor);
    }

這個方法本身沒有問題,問題出在transfer(newTable);這個方法是用來移動oldTable裏的數據到newTable裏。

 /**
     * 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++) {
			//(1)
            Entry<K,V> e = src[j];
            if (e != null) {
                src[j] = null;
                do {
					//(2)
                    Entry<K,V> next = e.next;
                    int i = indexFor(e.hash, newCapacity);
					//(3)
                    e.next = newTable[i];
                    newTable[i] = e;
                    e = next;
                } while (e != null);
            }
        }
    }

下面分析可能出現的情況,假設原來oldTable裏存放a1,a2的hash值是一樣的,那麼entry鏈表順序是:

P1:oldTable[i]->a1->a2->null

P2:oldTable[i]->a1->a2->null

線程P1運行到(1)下面這行時,e=a1(a1.next=a2),繼續運行到(2)下面時,next=a2。這個時候切換到線程P2,線程P2執行完這個鏈表的循環。如果恰a1,a2在新的table中的hash值又是一樣的,那麼此時的鏈表順序是: 

主存:newTable[i]->a2->a1->null

注意這個時候,a1,a2連接順序已經反了。現在cpu重新切回P1,在(3)這行以後:e.next = newTable[i];即:a1.next=newTable[i];

newTable[i]=a1;

e=a2;

開始第二次while循環(e=a2,next=a1):

a2.next=newTable[i];//也就是a2.next=a1

newTable[i]=a2

e=a1

開始第三次while循環(e=a1,next=null)

a1.next=newTable[i];//也就是a1.next=a2


這個時候a1.next=a2,a2.next=a1,形成迴環了,這樣就造成了死循環,在get操作的時候next永遠不爲null,造成死循環。

可以看到很偶然的情況下會出現死循環,不過一旦出現後果是非常嚴重的,多線程的環境還是應該用ConcurrentHashMap。

 

 

 

發佈了75 篇原創文章 · 獲贊 18 · 訪問量 38萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章