HashMap因爲多線程未同步時導致put進的元素get出來爲null的分析(轉)

     最近遇到併發往HahsMap裏put的情況,遇到相同的問題,分析很好,也懶得自己寫了,傳播一下別人的只是。
==============================
當你明明put進了一對非null key-value進了HashMap,某個時候你再用這個key去取的時候卻發現value爲null,再次取的時候卻又沒問題,都知道是HashMap的非線程安全特性引起的,分析具體原因如下:
Java代碼 
public get(Object key)  
        if (key == null)  
            return getForNullKey();  
        int hash hash(key.hashCode());  
        // indexFor方法取得key在table數組中的索引,table數組中的元素是一個鏈表結構,遍歷鏈表,取得對應key的value  
        for (Entry table[indexFor(hash, table.length)]; != null; e.next)  
            Object k;  
            if (e.hash == hash && ((k e.key) == key || key.equals(k)))  
                return e.value;  
         
        return null;  
 再看看put方法:
Java代碼 
public put(K key, value)  
        if (key == null)  
            return putForNullKey(value);  
        int hash hash(key.hashCode());  
        int indexFor(hash, table.length);  
        for (Entry table[i]; != null; e.next)  
            Object k;  
            if (e.hash == hash && ((k e.key) == key || key.equals(k)))  
                oldValue e.value;  
                e.value value;  
                e.recordAccess(this);  
                return oldValue;  
             
         
        modCount++;  
        // 若之前沒有put進該key,則調用該方法  
        addEntry(hash, key, value, i);  
        return null;  
再看看addEntry裏面的實現:
Java代碼 
void addEntry(int hash, key, value, int bucketIndex)  
        Entry table[bucketIndex];  
        table[bucketIndex] new Entry(hash, key, value, e);  
        if (size++ >= threshold)  
            resize(2 table.length);  
 裏面有一個if塊,當map中元素的個數(確切的說是元素的個數-1)大於或等於容量與加載因子的積時,裏面的resize是就會被執行到的,繼續resize方法:
Java代碼 
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裏面重新new一個Entry數組,其容量就是舊容量的2倍,這時候,需要重新根據hash方法將舊數組分佈到新的數組中,也就是其中的transfer方法:
Java代碼 
void transfer(Entry[] newTable)  
        Entry[] src table;  
        int newCapacity newTable.length;  
        for (int 0; src.length; j++)  
            Entry src[j];  
            if (e != null)  
                src[j] null;  
                do  
                    Entry next e.next;  
                    int indexFor(e.hash, newCapacity);  
                    e.next newTable[i];  
                    newTable[i] e;  
                    next;  
                while (e != null);  
             
         
在這個方法裏,將舊數組賦值給src,遍歷src,當src的元素非null時,就將src中的該元素置null,即將舊數組中的元素置null了,也就是這一句:
Java代碼 
if (e != null)  
        src[j] null;  
 此時若有get方法訪問這個key,它取得的還是舊數組,當然就取不到其對應的value了。
下面,我們重現一下場景:
Java代碼 
import java.util.HashMap;  
import java.util.Map;  
public class TestHashMap  
    public static void main(String[] args)  
        final Map map new HashMap(4, 0.5f);  
        new Thread(){  
            public void run()  
                while(true)   
                    System.out.println(map.get("name1"));  
                    try  
                        Thread.sleep(1000);  
                    catch (InterruptedException e)  
                        e.printStackTrace();  
                     
                 
             
        }.start();  
        for(int i=0; i<3; i++)  
            map.put("name" i, "value" i);  
         
Debug上面這段程序,在map.put處設置斷點,然後跟進put方法中,當i=2的時候就會發生resize操作,在transfer將元素置null處停留片刻,此時線程打印的值就變成null了。
 其它可能由未同步HashMap導致的問題:
1、多線程put後可能導致get死循環(主要問題在於put的時候transfer方法循環將舊數組中的鏈表移動到新數組)
2、多線程put的時候可能導致元素丟失(主要問題出在addEntry方法的new Entry(hash, key, value, e),如果兩個線程都同時取得了e,則他們下一個元素都是e,然後賦值給table元素的時候有一個成功有一個丟失)
總結:HashMap在併發程序中會產生許多微妙的問題,難以從表層找到原因。所以使用HashMap出現了違反直覺的現象,那麼可能就是併發導致的了
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章