JDK1.7 hashMap並發擴容死循環原理


JDK 1.7擴容的實現代碼

void resize(int newCapacity) { Entry[] oldTable = table; int oldCapacity = oldTable.length; ... Entry[] newTable = new Entry[newCapacity]; ... transfer(newTable, rehash); table = newTable; ----線程2如果設置table將丟失C threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1); }
即創建一個更大的數組,通過transfer方法,移動元素

void transfer(Entry[] newTable, boolean rehash) { int newCapacity = newTable.length; for (Entry<K,V> e : table) { while(null != e) { Entry<K,V> next = e.next; ----線程B執行到這里掛起(未執行) if (rehash) { e.hash = null == e.key ? 0 : hash(e.key); } int i = indexFor(e.hash, newCapacity); e.next = newTable[i]; newTable[i] = e; e = next; } } }
遍歷數組中每個位置的鏈表,對每個元素進行重新hash(rehash = true時,但實際上這玩意都是false,hash= old hash),在新的newTable以頭插法的方式插入。

假設有一個hashMap數組(正常是2的N次長度,這里方便舉例), 節點3上存有abc元素,此時發生擴容



此時假設有兩個線程

 

線程B在執行到Entry<K,V> next = e.next;后掛起,此時e指向元素a,e.next指向元素b

到線程A在new table的數組7位置依次用頭插法插入3個元素后

 

 

 此時線程B繼續執行以下代碼

Entry<K,V> next = e.next; //next = b
 e.next = newTable[i]; //數組7的地址賦予變量e.next
newTable[i] = e; //將a放到數組7的位置
e = next;  // e = next = b
執行結束的關系如圖

 

變量e = b不是null,循環繼續執行,

Entry<K,V> next = e.next; // next = a
 e.next = newTable[i];  //數組7地址指向e.next newTable[i] = e; //將b放到數組7的位置 e = next;    //e =next = a

執行后引用關系圖

 

 

 此時變量e = a仍舊不為空,繼續循環。。

Entry<K,V> next = e.next; // 變量a沒有next,所以next = null
 e.next = newTable[i];  // 因為newTable[i]存的是b,這一步相當於將a的next指向了b,於是問題出現了
newTable[i] = e; //將變量a放到數組7的位置
e
= next; // e= next = null



當在數組7遍歷節點尋找對應的key時, 節點a和b就發生了死循環, 直到cpu被完全耗盡。

另外,如果最終線程2執行了table = newTable;那元素C就發生了數據丟失問題

 

該問題在JDK1.8修復了(尾插法),並發還是用concurrent hashmap吧

 

本文圖示來自 https://www.jianshu.com/p/1e9cf0ac07f4


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM