0. HashMap 簡單說幾句
我們在學習 HashMap 的時候,都知道 HashMap 是非線程安全的,同時我們知道 HashTable 是線程安全的,因為里面的方法使用了 synchronized 進行同步。
但是 HashMap 為什么是非線程安全的呢?難道僅僅就是因為內部的方法沒有 synchronized 關鍵字修飾嗎?這篇文章主要來分析一下原因。
我們知道 HashMap 底層是一個 Entry 數組,當發生 hash 沖突的時候,HashMap 是采用鏈表的方式來解決的,在對應的數組位置存放鏈表的頭結點。對鏈表而言,新加入的節點會從頭結點加入。
HashMap為什么線程不安全,多線程并發的時候在什么情況下可能出現問題?
JAVAdoc中關于hashmap的一段描述如下:
此實現不是同步的。如果多個線程同時訪問一個哈希映射,而其中至少一個線程從結構上修改了該映射,則它必須 保持外部同步。(結構上的修改是指添加或刪除一個或多個映射關系的任何操作;僅改變與實例已經包含的鍵關聯的值不是結構上的修改。)這一般通過對自然封裝該映射的對象進行同步操作來完成。如果使用 Collections.synchronizedMap 方法來“包裝”該映射。最好在創建時完成這一操作,以防止對映射進行意外的非同步訪問,如下所示:
Map map = Collections.synchronizedMap(new HashMap<>());
1. HashMap 在插入的時候
在Hashmap做put操作的時候會調用到以上的addEntry方法。
現在假如A線程和B線程同時對同一個數組位置調用addEntry,兩個線程會同時得到現在的頭結點,然后A寫入新的頭結點之后,B也寫入新的頭結點,那B的寫入操作就會覆蓋A的寫入操作造成A的寫入操作丟失。
2. HashMap 在擴容的時候
addEntry中當加入新的鍵值對后鍵值對總數量超過門限值的時候會調用一個resize操作,代碼如下:
這個操作會新生成一個新的容量的數組,然后對原數組的所有鍵值對重新進行計算和寫入新的數組,之后指向新生成的數組。
HashMap 有個擴容的操作,這個操作會新生成一個新的容量的數組,然后對原數組的所有鍵值對重新進行計算和寫入新的數組,之后指向新生成的數組。
那么問題來了,當多個線程同時進來,檢測到總數量超過門限值的時候就會同時調用 resize 操作,各自生成新的數組并 rehash 后賦給該 map 底層的數組,結果最終只有最后一個線程生成的新數組被賦給該 map 底層,其他線程的均會丟失。而且當某些線程已經完成賦值而其他線程剛開始的時候,就會用已經被賦值的table作為原始數組,這樣也會有問題。
其他地方還有很多可能會出現線程安全問題,我就不一一列舉了,總之 HashMap 是非線程安全的,有并發問題時,建議使用 ConcrrentHashMap。






