面試官問:HashMap在並發情況下為什么造成死循環?一臉懵


這個問題是在面試時常問的幾個問題,一般在問這個問題之前會問Hashmap和HashTable的區別?面試者一般會回答:hashtable是線程安全的,hashmap是線程不安全的。

那么面試官就會緊接着問道,為什么hashmap不是線程安全的,會造成什么問題么?於是面試者就回答:HashMap在並發情況下的put操作會造成死循環。

這時候就會被面試官問:HashMap在並發為什么造成死循環?

很多面試者這時候就會一臉懵。沒有過相關經驗和深入的理解源碼是很難回答這個問題的。

下面我們就通過HahMap源碼來驗證下,多線程並發put操作為何會生成環形鏈表,產生死循環。

這是HashMap擴容的源碼

/**
 * Transfers all entries from current table to newTable. 
 */
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;
            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;
        } // while  

    }
}

開始之前先回顧一下HashMap的擴容機制:
HashMap默認設定的裝載因子為0.75(可改),HashMap的大小為length,已經裝載的元素數量為num,當( num / length )> 裝載因子時,
開始擴容

先創建一個散列表HashMap:Map<Integer> map = new HashMap<Integer>(2); ,裝載因子默認0.75,當插入第二個元素時,會發生擴容
我們先在map中放入6、8兩個元素。

插入后的狀態

這時有兩個線程都執行put操作,那么在此刻兩個線程都對HashMap進行擴容,這時候就注意在上文的源碼里注釋為(關鍵代碼)這一行:Entry<K,V> next = e.next;

假如兩個線程分別為A、B兩個線程。A線程在執行到關鍵代碼這一行線程就被掛起,那么此刻A線程中:e = 6; next = 8;

接着B線程開始進行擴容,假設新的散列表中,節點6 和 節點8 還是會產生散列沖突,那么線程B的擴容過程為:

  • 先申請一個空間為舊散列表兩倍大的空間

    申請兩倍大小的空間
  • 將節點6 遷移至新散列表

    節點6遷移至新散列表
  • 將節點8 遷移至新散列表

    將節點8 遷移至新散列表

此時線程B的擴容已經完成,節點8 的后繼節點為節點6 ,節點6的后繼節點為null。

我們將新舊兩個散列表做個對比:

對比

回顧一下線程A的當前狀態:e = 6; next = 8;,處於掛起狀態。接着A線程取消掛起狀態,接着執行(關鍵代碼)之后的代碼:將e = 6;節點遷移至新的散列表,並將next = 8的節點賦值給e。擴容並遷移節點6后的狀態,如下圖所示:

A線程擴容遷移節點6

於是第二次執行while循環時,當前待處理節點:e = 8;

在執行(關鍵代碼)這一行時,由於線程B在擴容時將節點8的后繼節點變為節點6,所以next不是為null,而是next = 6;

dsa

接着開始執行第三次while循環,由於節點6的后繼節點為null,所以 next = null;,執行完第三次while循環的結果為:

321312

循環結束。

可以看到擴容后的散列表中鏈表成環,如果這時候執行get()方法查詢,就會導致死循環。

總結

HashMap的方法不是線程安全的。HashMap在並發執行put操作時發生擴容,可能會導致節點丟失,產生環形鏈表等情況。

  • 節點丟失,會導致數據不准
  • 生成環形鏈表,會導致get()方法死循環。

知識拓展

在jdk1.7中,由於擴容時使用頭插法,在並發時可能會形成環狀列表,導致死循環,在jdk1.8中改為尾插法,可以避免這種問題,但是依然避免不了節點丟失的問題。

建議

HashMap的設計初衷就不是在並發情況下使用,如果有並發的場景,推薦使用ConcurrentHashMap

關注公眾號:java之旅


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM