天天看點

多線程下HashMap的死循環問題多線程下HashMap的問題:為何出現死循環?優秀部落格為什麼需要rehash

多線程下HashMap的問題:

1、多線程put操作後,get操作導緻死循環。

2、多線程put非NULL元素後,get操作得到NULL值。

3、多線程put操作,導緻元素丢失。

為何出現死循環?

大家都知道,HashMap采用連結清單解決Hash沖突,具體的HashMap的分析可以參考一下Java集合—HashMap源碼剖析 的分析。因為是連結清單結構,那麼就很容易形成閉合的鍊路,這樣在循環的時候隻要有線程對這個HashMap進行get操作就會産生死循環。但是,我好奇的是,這種閉合的鍊路是如何形成的呢。

在單線程情況下,隻有一個線程對HashMap的資料結構進行操作,是不可能産生閉合的回路的。那就隻有在多線程并發的情況下才會出現這種情況,那就是在put操作的時候,如果size>initialCapacity*loadFactor,那麼這時候HashMap就會進行rehash操作,随之HashMap的結構就會發生翻天覆地的變化。很有可能就是在兩個線程在這個時候同時觸發了rehash操作,産生了閉合的回路。

優秀部落格

https://cloud.tencent.com/developer/article/1444588

為什麼需要rehash

當多線程通路HashMap資料結構時,需要使用get和put方法,每一次的添加put都會需要計算值hashcode值,如果發現hash的資料長度大于設定容量,需要進行擴容,但是擴容需要進行把所有的資料結構(數組和連結清單)内的資料複制到新的資料結構中,這個就是transfer方法

關鍵的一步操作是transfer(newTable),這個操作會把目前Entry[] table數組的全部元素轉移到新的table中,

這個transfer的過程在并發環境下會發生錯誤,導緻數組連結清單中的連結清單形成循環連結清單,在後面的get操作時e = e.next操作無限循環,Infinite Loop出現。

多線程rehash的時候如何造成閉環連結清單

1)假設我們有兩個線程。我用紅色和淺藍色标注了一下。

我們再回頭看一下我們的 transfer代碼中的這個細節:

多線程下HashMap的死循環問題多線程下HashMap的問題:為何出現死循環?優秀部落格為什麼需要rehash
do {
    Entry<K,V> next = e.next;// <--假設線程一執行到這裡就被排程挂起了
    int i = indexFor(e.hash, newCapacity);
    e.next = newTable[i];
    newTable[i] = e;
    e = next;
} while (e != null);
           

而我們的線程二執行完成了。于是我們有下面的這個樣子。

多線程下HashMap的死循環問題多線程下HashMap的問題:為何出現死循環?優秀部落格為什麼需要rehash

注意,因為Thread1的 e 指向了key(3),而next指向了key(7),其線上程二rehash後,指向了線程二重組後的連結清單。我們可以看到連結清單的順序被反轉後。

2)線程一被排程回來執行。

先是執行 newTalbe[i] = e;

然後是e = next,導緻了e指向了key(7),

而下一次循環的next = e.next導緻了next指向了key(3)

多線程下HashMap的死循環問題多線程下HashMap的問題:為何出現死循環?優秀部落格為什麼需要rehash

3)一切安好。

線程一接着工作。把key(7)摘下來,放到newTable[i]的第一個,然後把e和next往下移

多線程下HashMap的死循環問題多線程下HashMap的問題:為何出現死循環?優秀部落格為什麼需要rehash

4)環形連結出現。

e.next = newTable[i] 導緻 key(3).next 指向了 key(7)

注意:此時的key(7).next 已經指向了key(3), 環形連結清單就這樣出現了

多線程下HashMap的死循環問題多線程下HashMap的問題:為何出現死循環?優秀部落格為什麼需要rehash

于是,當我們的線程一調用到,HashTable.get(11)時,悲劇就出現了——Infinite Loop。