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。避免悲剧的发生