標籤:
最近項目中出現了Tomcat佔用CPU100%的情況,原以為是代碼中出現死迴圈,後台使用jstack做了dump,發現是系統中不合理使用HashMap導致出現了死迴圈(注意不是死結)。
產生這個死迴圈的根源在於對一個未保護的共用變數 — 一個"HashMap"資料結構的操作。當在所有操作的方法上加了"synchronized"後,一切恢複了正常。
這算jvm的bug嗎?應該說不是的,這個現象很早以前就報告出來了(詳細見:http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6423457)。Sun的工程師並不認為這是bug,而是建議在這樣的情境下應採用"ConcurrentHashMap",
回複中的原話:
This is a classic symptom of an incorrectly synchronized use ofHashMap. Clearly, the submitters need to use a thread-safeHashMap. If they upgraded to Java 5, they could just use ConcurrentHashMap.
所以在開發過程中應當注意這點,在多線程的環境下,盡量使用ConcurrentHashMap。
可能出現問題的地方是在擴容的時候
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);
- }
這個方法本身沒有問題,問題出在transfer(newTable);這個方法是用來移動oldTable裡的資料到newTable裡。
Java代碼
- /**
- * 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);
- }
- }
- }
下面分析可能出現的情況,假設原來oldTable裡存放a1,a2的hash值是一樣的,那麼entry鏈表順序是:
P1:oldTable[i]->a1->a2->null
P2:oldTable[i]->a1->a2->null
線程P1運行到(1)下面這行時,e=a1(a1.next=a2),繼續運行到(2)下面時,next=a2。這個時候切換到線程P2,線程P2執行完這個鏈表的迴圈。如果恰a1,a2在新的table中的hash值又是一樣的,那麼此時的鏈表順序是:
主存:newTable[i]->a2->a1->null
注意這個時候,a1,a2串連順序已經反了。現在cpu重新切回P1,在(3)這行以後:
e.next = newTable[i];
即:a1.next=newTable[i];
newTable[i]=a1;
e=a2;
開始第二次while迴圈(e=a2,next=a1):
a2.next=newTable[i];//也就是a2.next=a1
newTable[i]=a2
e=a1
開始第三次while迴圈(e=a1,next=null)
a1.next=newTable[i];//也就是a1.next=a2
這個時候a1.next=a2,a2.next=a1,形成迴環了,這樣就造成了死迴圈,在get操作的時候next永遠不為null,造成死迴圈。
可以看到很偶然的情況下會出現死迴圈,不過一旦出現後果是非常嚴重的,多線程的環境還是應該用ConcurrentHashMap。
http://shuaijie506.iteye.com/blog/1815213
http://blog.163.com/huxb23%40126/blog/static/625898182011211318854/
java中HashMap在多線程環境下引起CPU100%的問題解決(轉)