hashmap引發死循環

今天開發環境壓測的時候出現cpu用滿了狀況,看線程堆棧,一堆線程都停留在org.apache.commons.collections4.map.AbstractHashedMap.put(AbstractHashedMap.java:285),查看google源代碼html

public Object put(Object key, Object value) {
        key = convertKey(key);
        int hashCode = hash(key);
        int index = hashIndex(hashCode, data.length);
        HashEntry entry = data[index];
        while (entry != null) {
            if (entry.hashCode == hashCode && isEqualKey(key, entry.key)) {
                Object oldValue = entry.getValue();
                updateEntry(entry, value);
                return oldValue;
            }
            entry = entry.next;
        }
        
        addMapping(index, hashCode, key, value);
        return null;
    }java

方法是非線程安全的,而addMapping方法會觸發ensureCapacity擴容,而併發擴容就會容易致使死循環, 具體緣由參考文章https://www.cnblogs.com/dongguacai/p/5599100.html算法

解決方法:apache

一、使用ConcurrentHashMap。數組

二、使用Collections.synchronizedMap(Mao<K,V> m)方法把HashMap變成一個線程安全的Map。安全

 

 

我copy了部分文章內容併發

正常的擴容過程

咱們先來看下單線程狀況下,正常的rehash過程app

一、假設咱們的hash算法是簡單的key mod一下表的大小(即數組的長度)。測試

二、最上面是old hash表,其中HASH表的size=2,因此key=3,5,7在mod 2 之後都衝突在table[1]這個位置上了。google

三、接下來HASH表擴容,resize=4,而後全部的<key,value>從新進行散列分佈,過程以下:

 

 

 

在單線程狀況下,一切看起來都很美妙,擴容過程也至關順利。接下來看下併發狀況下的擴容。

併發狀況下的擴容

一、首先假設咱們有兩個線程,分別用紅色和藍色標註了。

二、擴容部分的源代碼:

複製代碼
 1 void transfer(Entry[] newTable) {  2 Entry[] src = table;  3 int newCapacity = newTable.length;  4 for (int j = 0; j < src.length; j++) {  5 Entry<K,V> e = src[j];  6 if (e != null) {  7 src[j] = null;  8 do {  9 Entry<K,V> next = e.next; 10 int i = indexFor(e.hash, newCapacity); 11 e.next = newTable[i]; 12 newTable[i] = e; 13 e = next; 14 } while (e != null); 15  } 16  } 17 }
複製代碼

三、若是在線程一執行到第9行代碼就被CPU調度掛起,去執行線程2,且線程2把上面代碼都執行完畢。咱們來看看這個時候的狀態:

 

 

四、接着CPU切換到線程一上來,執行8-14行代碼,首先安置3這個Entry:

 

這裏須要注意的是:線程二已經完成執行完成,如今table裏面全部的Entry都是最新的,就是說7的next是3,3的next是null;如今第一次循環已經結束,3已經安置穩當。看看接下來會發生什麼事情:

一、e=next=7;

二、e!=null,循環繼續

三、next=e.next=3

四、e.next 7的next指向3

五、放置7這個Entry,如今如圖所示:

放置7以後,接着運行代碼:

一、e=next=3;

二、判斷不爲空,繼續循環

三、next= e.next  這裏也就是3的next 爲null

四、e.next=7,就3的next指向7.

五、放置3這個Entry,此時的狀態如圖: 

這個時候其實就出現了死循環了,3移動節點頭的位置,指向7這個Entry;在這以前7的next同時也指向了3這個Entry。

代碼接着往下執行,e=next=null,此時條件判斷會終止循環。此次擴容結束了。可是後續若是有查詢(不管是查詢的迭代仍是擴容),都會hang死在table【3】這個位置上。如今回過來看文章開頭的那個Demo,就是掛死在擴容階段的transfer這個方法上面。

出現上面這種狀況毫不是我要在測試環境弄一批數據專門爲了演示這種問題。咱們仔細思考一下就會得出這樣一個結論:若是擴容前相鄰的兩個Entry在擴容後仍是分配到相同的table位置上,就會出現死循環的BUG。在複雜的生產環境中,這種狀況儘管不常見,可是可能會碰到。

相關文章
相關標籤/搜索