今天在看《分佈式java應用》這本書的時候看到作者提到HashMap在多線程併發的環境下有可能出現死循環,導致cpu100%的現象,看了下源碼結合網上的分析說明下這種可能性。可能出現問題的地方是在擴容的時候
- 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(newTable);這個方法是用來移動oldTable裏的數據到newTable裏。
- /**
- * Transfers all entries from current table to newTable.
- */
- void transfer(Entry[] newTable) {
- Entry[] src = table;
- int newCapacity = newTable.length;
- for (int j = 0; j < src.length; j++) {
- //(1)
- Entry<K,V> e = src[j];
- if (e != null) {
- src[j] = null;
- do {
- //(2)
- Entry<K,V> next = e.next;
- int i = indexFor(e.hash, newCapacity);
- //(3)
- e.next = newTable[i];
- newTable[i] = e;
- e = next;
- } while (e != null);
- }
- }
- }
下面分析可能出現的情況,假設原來table裏存放的entry鏈表順序是:
oldTable[i]:a1,a2,null
線程P1運行到(1)下面這行時,e=a1(a1.next=a2),執行到(2)下面時:next=e.next=a2。這個時候切換到線程P2,線程P2執行完這個鏈表的循環,如果剛好a1和a2在newTable裏的i值相同(int i = indexFor(e.hash, newCapacity);),那麼此時的鏈表順序是:
newTable[i]:a2(next=a1),a1,null
現在cpu重新切回P1,在(3)這行:e.next = newTable[i];即:a1.next=newTable[i]=a2;
然後:newTable[i]=e=a1;e = next=a2;可以看到這個時候a1.next=a2,a2.next=a1,形成迴環了,這樣就造成了死循環.下面是針對各個線程各變量的情況
init(初始值):a1.next=a2,a2.next=null
P1:e=a1,next=e.next=a2; waiting
P2:a2.next=a1,a1.next=null ;notify
P1:e.next=a1.next=newTable[i]=a2; newTable[i]=a1,e=next=a2
end:a1.next=a2;a2.next=a1(P2結束後產生的結果)
可以看到很偶然的情況下會出現死循環,不過一旦出現後果是非常嚴重的,多線程的環境還是應該用ConcurrentHashMap。