HashMap在高併發下引發的死循環

HashMap事實上並不是線程安全的,在高併發的狀況下,是很是可能發生死循環的,由此形成CPU 100%,這是很是可怕的。因此在多線程的狀況下,用HashMap是很是不穩當的行爲,應採用線程安全類ConcurrentHashMap進行取代。java


HashMap死循環緣由

HashMap進行存儲時,假設size超過當前最大容量*負載因子時候會發生resize。首先看一下resize原代碼安全


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);
    }

而這段代碼中又調用了transfer()方法,而這種方法實現的機制就是將每個鏈表轉化到新鏈表,並且鏈表中的位置發生反轉,而這在多線程狀況下是很是easy形成鏈表迴路。從而發生get()死循環。咱們看一下他的源碼

void transfer(Entry[] newTable) {
        Entry[] src = table;
        int newCapacity = newTable.length;
        for (int j = 0; j < src.length; j++) {
            Entry<K,V> e = src[j];
            if (e != null) {
                src[j] = null;
                do {
                    Entry<K,V> next = e.next;
                    int i = indexFor(e.hash, newCapacity);
                    e.next = newTable[i];
                    newTable[i] = e;
                    e = next;
                } while (e != null);
            }
        }
    }

HashMap死循環演示

假若有兩個線程P一、P2,以及鏈表 a=》b=》null

一、P1先運行,運行完"Entry<K,V> next = e.next;"代碼後發生堵塞,或者其它狀況再也不運行下去,此時e=a。next=b多線程

二、而P2已經運行完整段代碼,因而當前的新鏈表newTable[i]爲b=》a=》null併發

三、P1又繼續運行"Entry<K,V> next = e.next;"以後的代碼,則運行完"e=next;"後,newTable[i]爲a《=》b。則形成迴路,while(e!=null)一直死循環高併發


總結

HashMap並非線程安全,因此在多線程狀況下,應該首先考慮用ConcurrentHashMap。避免悲劇的發生
相關文章
相關標籤/搜索