HashMap1.8中多執行緒擴容引起的死迴圈問題
阿新 • • 發佈:2019-02-17
最近在學習併發,看到書上寫到hashmap在併發執行put操作時會引起死迴圈,因為在put中會引起擴容操作,使連結串列形成環形的資料結構,不是很明白,然後在網上看了一些部落格,但是部落格都是jdk1.7版本的,而1.8版本中的擴容操作已經和1.7版本中大不一樣了,於是自己開始研究,看原始碼的時候,覺得jdk1.8版本中多執行緒put不會在出現死迴圈問題了,只有可能出現數據丟失的情況,因為1.8版本中,會將原來的連結串列結構儲存在節點e中,然後依次遍歷e,根據hash&n是否等於0,分成兩條支鏈,儲存在新陣列中。jdk1.7版本中,擴容過程中會新陣列會和原來的陣列有指標引用關係,所以將引起死迴圈問題。
jdk1.8擴容程式碼
final Node< K,V >[] resize() { Node< K,V >[] oldTab = table; int oldCap = (oldTab == null) ? 0 : oldTab.length; int oldThr = threshold; int newCap, newThr = 0; if (oldCap > 0) { if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } //在容量不超過做大容量的時候,擴容擴大為原來的兩倍 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // double threshold } ...省略部分程式碼 @SuppressWarnings({"rawtypes","unchecked"}) Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; table = newTab; //遍歷舊陣列中的元素,複製到table陣列中 if (oldTab != null) { for (int j = 0; j < oldCap; ++j) { Node<K,V> e; //在這裡可能會出現資料丟失 if ((e = oldTab[j]) != null) { oldTab[j] = null; if (e.next == null) newTab[e.hash & (newCap - 1)] = e; else if (e instanceof TreeNode) ((TreeNode< K,V >)e).split(this, newTab, j, oldCap); else { // preserve order Node< K,V > loHead = null, loTail = null; Node< K,V > hiHead = null, hiTail = null; Node< K,V > next; do { next = e.next; if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); if (loTail != null) { loTail.next = null; newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } return newTab; } .
測試程式碼1:
public class TestHashMap { private static HashMap< Integer, Integer > map = new HashMap<>(2); public static void main(String[] args) throws InterruptedException { //執行緒1 Thread t1 = new Thread(new Runnable() { @Override public void run() { for (int i = 1; i <= 100000; i++) { int result = i; new Thread(new Runnable() { @Override public void run() { map.put(result, result); } }, "ftf" + i).start(); } } }); t1.start(); //讓主執行緒睡眠5秒,保證執行緒1和執行緒2執行完畢 Thread.sleep(5000); for (int i= 1; i <= 100000; i++) { //檢測資料是否發生丟失 Integer value = map.get(i); if (value==null) { System.out.println(i + "資料丟失"); } } System.out.println("end..."); } }
此時插入100000條資料,沒有引起死迴圈和資料丟失
繼續增大資料量:
此時插入100000條資料,沒有引起死迴圈和資料丟失
繼續增大資料量:資料增加到1000000,出現java.lang.OutOfMemoryError,棧記憶體溢位,重新調整jvm棧區記憶體的大小
如何調整:深入理解jvm書中寫到,如果是建立過多執行緒導致記憶體溢位,在不能減少執行緒數量或者更換64位虛擬機器的情況下,只能通過減少最大堆和減少棧容量來換取更多的執行緒。
在idea中修改jvm引數:-Xss120k,減少一個執行緒分配的棧記憶體(預設為1m)
調整以後,成勳正常執行,並且出現數據丟失現象,但是仍沒有出現死迴圈現象
執行結果部分:
value:null資料丟失
value:null資料丟失
value:null資料丟失
value:null資料丟失
value:null資料丟失
end...
出現了很多null值,這裡只是一部分
以上僅是lz自己的觀點,有錯誤歡迎指導討論