正如上篇文中所說,HashMap不是線程安全的,在被多線程共享操作時,會有問題,具體什麼問題呢,一直沒有個清晰的了解,今天寫了個測試程式調了一下,才明白其中道理。
主要是多線程同時put時,如果同時觸發了rehash操作,會導緻HashMap中的連結清單中出現循環節點,進而使得後面get的時候,會死循環。【關于什麼是rehash,讀者可以自行去google了】
本文主要參考了:http://coolshell.cn/articles/9606.html,測試資料也一樣。
測試代碼:
import java.util.HashMap;
public class HashMapInfiniteLoop {
private static HashMap<Integer, Integer> map = new HashMap<Integer, Integer>(2,0.75f);
public static void main(String[] args) {
map.put(5, 55);
new Thread("Thread1") {
public void run() {
map.put(7, 77);
System.out.println(map);
};
}.start();
new Thread("Thread2") {
public void run() {
map.put(3, 33);
System.out.println(map);
};
}.start();
}
}
其中,map初始化為一個長度為2的數組,loadFactor=0.75,threshold=2*0.75=1,也就是說當put第二個key的時候,map就需要進行rehash:

的
下面開始調試運作測試程式
(1)首先使Thread1和Thread2都停在run()的第一行:
此時map的内容為:
(2)Thread1調試進入JDK源代碼,停在HashMap.transfer()方法内第一行,切換到Thread2,也停在HashMap.transfer()方法内第一行:
此時map的内容為:
(3)切換到Thread1, 停在HashMap.transfer()方法内477行【src[j] = null;】;切換到Thread2,也停在477行
(4)切換回Thread1,繼續執行,停在圖中480行:【int i = indexFor(e.hash, newCapacity);】
此時map内容沒變,隻是添加了代碼中的e、next指針:
(5)切換回Thread2,直接把Thread2執行完畢:
此時map的内容為:
(6)切換回Thread1,回到transfer()方法中:
此時從調試資訊中還可以清晰地看出map的内容:{5=55, 7=77, 3=33}
此時map形如:
(7)繼續Thread1中的do-while循環,第一次循環過後,依舊【停在480行】:
(8)第二次循環過後,依舊【停在480行】:
(8)第二次循環過後,e已經為null,跳出循環:
(9)離開transfer()方法,回到resize方法中,将newTable指派給map.table,此時在檢視map,預設的toString()方法已經在死循環了
至此,map中數組索引位置為3的連結清單上已經成功的出現了環,再對map做索引位置為3的get操作,就會死循環在這裡,CPU成功到達100%。
比如,調用map.get(11)時,即會引起死循環。
而且,map中還丢失了元素,(5,55)已經不再map中了。