HashMap爲何線程不安全(hash碰撞與擴容致使)

    一直以來都知道HashMap是線程不安全的,可是到底爲何線程不安全,在多線程操做狀況下何時線程不安全?java

讓咱們先來了解一下HashMap的底層存儲結構,HashMap底層是一個Entry數組,一旦發生Hash衝突的的時候,HashMap採用拉鍊法解決碰撞衝突,Entry內部的變量:數組

 

[java]  view plain  copy
 
  1. final Object key;  
  2. Object value;  
  3. Entry next;  
  4. int hash;  


        經過Entry內部的next變量能夠知道使用的是鏈表,這時候咱們能夠知道,若是多個線程,在某一時刻同時操做HashMap並執行put操做,而有大於兩個key的hash值相同,如圖中a一、a2,這個時候須要解決碰撞衝突,而解決衝突的辦法上面已經說過,對於鏈表的結構在這裏再也不贅述,暫且不討論是從鏈表頭部插入仍是從尾部初入,這個時候兩個線程若是剛好都取到了對應位置的頭結點e1,而最終的結果可想而知,a一、a2兩個數據中勢必會有一個會丟失,如圖所示:安全

 

再來看下put方法多線程

 

[java]  view plain  copy
 
  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方法:this

 

 

[java]  view plain  copy
 
  1. void addEntry(int i, Object obj, Object obj1, int j)  
  2.     {  
  3.         if(size >= threshold && null != table[j])  
  4.         {  
  5.             resize(2 * table.length);  
  6.             i = null == obj ? 0 : hash(obj);  
  7.             j = indexFor(i, table.length);  
  8.         }  
  9.         createEntry(i, obj, obj1, j);  
  10.     }  

addEntry方法依然不是同步的,因此致使了線程不安全出現傷處問題,其餘相似操做再也不說明,源碼一看便知,下面主要說一下另外一個很是重要的知識點,一樣也是HashMap非線程安全的緣由,咱們知道在HashMap存在擴容的狀況,對應的方法爲HashMap中的resize方法:spa

 

 

[java]  view plain  copy
 
  1. void resize(int i)  
  2.     {  
  3.         Entry aentry[] = table;  
  4.         int j = aentry.length;  
  5.         if(j == 1073741824)  
  6.         {  
  7.             threshold = 2147483647;  
  8.             return;  
  9.         } else  
  10.         {  
  11.             Entry aentry1[] = new Entry[i];  
  12.             transfer(aentry1, initHashSeedAsNeeded(i));  
  13.             table = aentry1;  
  14.             threshold = (int)Math.min((float)i * loadFactor, 1.073742E+009F);  
  15.             return;  
  16.         }  
  17.     }  


         能夠看到擴容方法也不是同步的,經過代碼咱們知道在擴容過程當中,會新生成一個新的容量的數組,而後對原數組的全部鍵值對從新進行計算和寫入新的數組,以後指向新生成的數組。.net

 


        當多個線程同時檢測到總數量超過門限值的時候就會同時調用resize操做,各自生成新的數組並rehash後賦給該map底層的數組table,結果最終只有最後一個線程生成的新數組被賦給table變量,其餘線程的均會丟失。並且當某些線程已經完成賦值而其餘線程剛開始的時候,就會用已經被賦值的table做爲原始數組,這樣也會有問題。線程

相關文章
相關標籤/搜索