併發程式設計二:HashMap怎麼會死鎖呢?
通過原始碼知道:HashMap的基本資料結構是Entry[] table。每個entry=table[i]都有next屬性,它其實只指向和entry有相同hash值的下一個物件,也就是同一個桶中的下一個資料。
HashMap這個資料結構不是執行緒安全的,幾乎所有人都知道。當我們採用多執行緒進行讀寫的時候必然會存線上程同步的問題。那怎麼會產生死鎖呢?這是我的一個作業。雖然在我的電腦上執行不出來。後來在網上找到了資料:誰能幫忙解釋一下為什麼這個程式會死鎖?
我找到的答案。就是在transfer函式中。我來貼下原始碼。
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); } /** * 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++) { 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); } } }
resize函式是發生在容量不夠的情況下的。當申請完了新的記憶體空間後,我們要把原來的記憶體空間中的資料轉移到新記憶體空間中去。也就是transfer函式。
假設現在有三個執行緒:T1,T2,T3;在老陣列中的第一個資料也就是e引用的物件,我們稱它為A,在新陣列中的頭兩個資料分別為B和C,B.next=C。
執行緒T1執行到e.next=newTable[i]時執行緒T2執行到next=e.next;然後執行緒t1去繼續
執行,會產生什麼效果呢?A.next=B,B.next=C。e指向的物件是B,newTable[i]=A。然後繼續執行,e.next=newTable[i],也就是B.next=A;同時A.next=B,繼續執行newTable[i] = e,e = next;如果沒有其它執行緒搗亂的話,那麼此時e應該是C啊,可惜只是如果,如果有第三個執行緒T3線上程T1執行e.next = newTable[i]的時候去執行next = e.next;那麼就中途改變了next的值,本來是儲存C的,但是現在成了A了。總結下現在的情況:e引用A,nextTable[i]引用B,A.next=B,B.next=A。現在明白了吧。會一直這麼死鎖下去的。
以上純屬個人見解,如果哪裡有問題,望大神指出。