多線程下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代碼中的這個細節:

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);
而我們的線程二執行完成了。于是我們有下面的這個樣子。
注意,因為Thread1的 e 指向了key(3),而next指向了key(7),其線上程二rehash後,指向了線程二重組後的連結清單。我們可以看到連結清單的順序被反轉後。
2)線程一被排程回來執行。
先是執行 newTalbe[i] = e;
然後是e = next,導緻了e指向了key(7),
而下一次循環的next = e.next導緻了next指向了key(3)
3)一切安好。
線程一接着工作。把key(7)摘下來,放到newTable[i]的第一個,然後把e和next往下移
4)環形連結出現。
e.next = newTable[i] 導緻 key(3).next 指向了 key(7)
注意:此時的key(7).next 已經指向了key(3), 環形連結清單就這樣出現了
于是,當我們的線程一調用到,HashTable.get(11)時,悲劇就出現了——Infinite Loop。