HashMap底層是一個Entry數組,當發生hash衝突的時候,hashmap是採用鏈表的方式來解決的,在對應的數組位置存放鏈表的頭結點。對鏈表而言,新加入的節點會從頭結點加入。數組
咱們來分析一下多線程訪問:多線程
1.在hashmap作put操做的時候會調用下面方法:this
// 新增Entry。將「key-value」插入指定位置,bucketIndex是位置索引。 void addEntry(int hash, K key, V value, int bucketIndex) { // 保存「bucketIndex」位置的值到「e」中 Entry<K,V> e = table[bucketIndex]; // 設置「bucketIndex」位置的元素爲「新Entry」, // 設置「e」爲「新Entry的下一個節點」 table[bucketIndex] = new Entry<K,V>(hash, key, value, e); // 若HashMap的實際大小 不小於 「閾值」,則調整HashMap的大小 if (size++ >= threshold) resize(2 * table.length); }
在hashmap作put操做的時候會調用到以上的方法。如今假如A線程和B線程同時對同一個數組位置調用addEntry,兩個線程會同時獲得如今的頭結點,而後A寫入新的頭結點以後,B也寫入新的頭結點,那B的寫入操做就會覆蓋A的寫入操做形成A的寫入操做丟失spa
2.刪除鍵值對的代碼線程
<span style="font-size: 18px;"> </span>// 刪除「鍵爲key」的元素 final Entry<K,V> removeEntryForKey(Object key) { // 獲取哈希值。若key爲null,則哈希值爲0;不然調用hash()進行計算 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; // 刪除鏈表中「鍵爲key」的元素 // 本質是「刪除單向鏈表中的節點」 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; }
當多個線程同時操做同一個數組位置的時候,也都會先取得如今狀態下該位置存儲的頭結點,而後各自去進行計算操做,以後再把結果寫會到該數組位置去,其實寫回的時候可能其餘的線程已經就把這個位置給修改過了,就會覆蓋其餘線程的修改。code
3.addEntry中當加入新的鍵值對後鍵值對總數量超過門限值的時候會調用一個resize操做,代碼以下:blog
// 從新調整HashMap的大小,newCapacity是調整後的容量 void resize(int newCapacity) { Entry[] oldTable = table; int oldCapacity = oldTable.length; //若是就容量已經達到了最大值,則不能再擴容,直接返回 if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } // 新建一個HashMap,將「舊HashMap」的所有元素添加到「新HashMap」中, // 而後,將「新HashMap」賦值給「舊HashMap」。 Entry[] newTable = new Entry[newCapacity]; transfer(newTable); table = newTable; threshold = (int)(newCapacity * loadFactor); }
這個操做會新生成一個新的容量的數組,而後對原數組的全部鍵值對從新進行計算和寫入新的數組,以後指向新生成的數組。索引
當多個線程同時檢測到總數量超過門限值的時候就會同時調用resize操做,各自生成新的數組並rehash後賦給該map底層的數組table,結果最終只有最後一個線程生成的新數組被賦給table變量,其餘線程的均會丟失。並且當某些線程已經完成賦值而其餘線程剛開始的時候,就會用已經被賦值的table做爲原始數組,這樣也會有問題。ci