一直以來都知道HashMap是線程不安全的,但是到底為什么線程不安全,在多線程操作情況下什么時候線程不安全?
讓我們先來了解一下HashMap的底層存儲結構,HashMap底層是一個Entry數組,一旦發生Hash沖突的的時候,HashMap采用拉鏈法解決碰撞沖突,Entry內部的變量:
final Object key; Object value; Entry next; int hash;
通過Entry內部的next變量可以知道使用的是鏈表,這時候我們可以知道,如果多個線程,在某一時刻同時操作HashMap並執行put操作,而有大於兩個key的hash值相同,如圖中a1、a2,這個時候需要解決碰撞沖突,而解決沖突的辦法上面已經說過,對於鏈表的結構在這里不再贅述,暫且不討論是從鏈表頭部插入還是從尾部初入,這個時候兩個線程如果恰好都取到了對應位置的頭結點e1,而最終的結果可想而知,a1、a2兩個數據中勢必會有一個會丟失,如圖所示:
再來看下put方法
1 public Object put(Object obj, Object obj1) 2 { 3 if(table == EMPTY_TABLE) 4 inflateTable(threshold); 5 if(obj == null) 6 return putForNullKey(obj1); 7 int i = hash(obj); 8 int j = indexFor(i, table.length); 9 for(Entry entry = table[j]; entry != null; entry = entry.next) 10 { 11 Object obj2; 12 if(entry.hash == i && ((obj2 = entry.key) == obj || obj.equals(obj2))) 13 { 14 Object obj3 = entry.value; 15 entry.value = obj1; 16 entry.recordAccess(this); 17 return obj3; 18 } 19 } 20 21 modCount++; 22 addEntry(i, obj, obj1, j); 23 return null; 24 }
put方法不是同步的,同時調用了addEntry方法:
void addEntry(int i, Object obj, Object obj1, int j) { if(size >= threshold && null != table[j]) { resize(2 * table.length); i = null == obj ? 0 : hash(obj); j = indexFor(i, table.length); } createEntry(i, obj, obj1, j); }
addEntry方法依然不是同步的,所以導致了線程不安全出現傷處問題,其他類似操作不再說明,源碼一看便知,下面主要說一下另一個非常重要的知識點,同樣也是HashMap非線程安全的原因,我們知道在HashMap存在擴容的情況,對應的方法為HashMap中的resize方法:
void resize(int i) { Entry aentry[] = table; int j = aentry.length; if(j == 1073741824) { threshold = 2147483647; return; } else { Entry aentry1[] = new Entry[i]; transfer(aentry1, initHashSeedAsNeeded(i)); table = aentry1; threshold = (int)Math.min((float)i * loadFactor, 1.073742E+009F); return; } }
可以看到擴容方法也不是同步的,通過代碼我們知道在擴容過程中,會新生成一個新的容量的數組,然后對原數組的所有鍵值對重新進行計算和寫入新的數組,之后指向新生成的數組。
當多個線程同時檢測到總數量超過門限值的時候就會同時調用resize操作,各自生成新的數組並rehash后賦給該map底層的數組table,結果最終只有最后一個線程生成的新數組被賦給table變量,其他線程的均會丟失。而且當某些線程已經完成賦值而其他線程剛開始的時候,就會用已經被賦值的table作為原始數組,這樣也會有問題。
本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。
一直以來都知道HashMap是線程不安全的,但是到底為什么線程不安全,在多線程操作情況下什么時候線程不安全?
讓我們先來了解一下HashMap的底層存儲結構,HashMap底層是一個Entry數組,一旦發生Hash沖突的的時候,HashMap采用拉鏈法解決碰撞沖突,Entry內部的變量:
通過Entry內部的next變量可以知道使用的是鏈表,這時候我們可以知道,如果多個線程,在某一時刻同時操作HashMap並執行put操作,而有大於兩個key的hash值相同,如圖中a1、a2,這個時候需要解決碰撞沖突,而解決沖突的辦法上面已經說過,對於鏈表的結構在這里不再贅述,暫且不討論是從鏈表頭部插入還是從尾部初入,這個時候兩個線程如果恰好都取到了對應位置的頭結點e1,而最終的結果可想而知,a1、a2兩個數據中勢必會有一個會丟失,如圖所示:
再來看下put方法
put方法不是同步的,同時調用了addEntry方法:
addEntry方法依然不是同步的,所以導致了線程不安全出現傷處問題,其他類似操作不再說明,源碼一看便知,下面主要說一下另一個非常重要的知識點,同樣也是HashMap非線程安全的原因,我們知道在HashMap存在擴容的情況,對應的方法為HashMap中的resize方法:
可以看到擴容方法也不是同步的,通過代碼我們知道在擴容過程中,會新生成一個新的容量的數組,然后對原數組的所有鍵值對重新進行計算和寫入新的數組,之后指向新生成的數組。
當多個線程同時檢測到總數量超過門限值的時候就會同時調用resize操作,各自生成新的數組並rehash后賦給該map底層的數組table,結果最終只有最后一個線程生成的新數組被賦給table變量,其他線程的均會丟失。而且當某些線程已經完成賦值而其他線程剛開始的時候,就會用已經被賦值的table作為原始數組,這樣也會有問題。