為什麼HashMap是執行緒不安全類?
阿新 • • 發佈:2019-02-04
本文轉載自:http://blog.csdn.net/mydreamongo/article/details/8960667
一直以來只是知道HashMap是執行緒不安全的,但是到底HashMap為什麼執行緒不安全,多執行緒併發的時候在什麼情況下可能出現問題?
HashMap底層是一個Entry陣列,當發生hash衝突的時候,hashmap是採用連結串列的方式來解決的,在對應的陣列位置存放連結串列的頭結點。對連結串列而言,新加入的節點會從頭結點加入。
javadoc中關於hashmap的一段描述如下:
此實現不是同步的。如果多個執行緒同時訪問一個雜湊對映,而其中至少一個執行緒從結構上修改了該對映,則它必須
Collections.synchronizedMap
方法來“包裝”該對映。最好在建立時完成這一操作,以防止對對映進行意外的非同步訪問,如下所示:
Map m = Collections.synchronizedMap(new HashMap(...));
1、
-
void addEntry(int hash, K key, V value, int bucketIndex) {
- Entry<K,V> e = table[bucketIndex];
- table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
- if (size++ >= threshold)
- resize(2 * table.length);
- }
在hashmap做put操作的時候會呼叫到以上的方法。現在假如A執行緒和B執行緒同時對同一個陣列位置呼叫addEntry,兩個執行緒會同時得到現在的頭結點,然後A寫入新的頭結點之後,B也寫入新的頭結點,那B的寫入操作就會覆蓋A的寫入操作造成A的寫入操作丟失
2、
- final Entry<K,V> removeEntryForKey(Object key) {
- int hash = (key == null) ? 0 : hash(key.hashCode());
- int i = indexFor(hash, table.length);
- Entry<K,V> prev = table[i];
- Entry<K,V> e = prev;
- while (e != null) {
- Entry<K,V> next = e.next;
- Object k;
- if (e.hash == hash &&
- ((k = e.key) == key || (key != null && key.equals(k)))) {
- modCount++;
- size--;
- if (prev == e)
- table[i] = next;
- else
- prev.next = next;
- e.recordRemoval(this);
- return e;
- }
- prev = e;
- e = next;
- }
- return e;
- }
刪除鍵值對的程式碼如上:
當多個執行緒同時操作同一個陣列位置的時候,也都會先取得現在狀態下該位置儲存的頭結點,然後各自去進行計算操作,之後再把結果寫會到該陣列位置去,其實寫回的時候可能其他的執行緒已經就把這個位置給修改過了,就會覆蓋其他執行緒的修改
3、addEntry中當加入新的鍵值對後鍵值對總數量超過門限值的時候會呼叫一個resize操作,程式碼如下:
- void resize(int newCapacity) {
- Entry[] oldTable = table;
- int oldCapacity = oldTable.length;
- if (oldCapacity == MAXIMUM_CAPACITY) {
- threshold = Integer.MAX_VALUE;
- return;
- }
- Entry[] newTable = new Entry[newCapacity];
- transfer(newTable);
- table = newTable;
- threshold = (int)(newCapacity * loadFactor);
- }
這個操作會新生成一個新的容量的陣列,然後對原陣列的所有鍵值對重新進行計算和寫入新的陣列,之後指向新生成的陣列。
當多個執行緒同時檢測到總數量超過門限值的時候就會同時呼叫resize操作,各自生成新的陣列並rehash後賦給該map底層的陣列table,結果最終只有最後一個執行緒生成的新陣列被賦給table變數,其他執行緒的均會丟失。而且當某些執行緒已經完成賦值而其他執行緒剛開始的時候,就會用已經被賦值的table作為原始陣列,這樣也會有問題。