1. 程式人生 > >HashMap1.8中多執行緒擴容引起的死迴圈問題

HashMap1.8中多執行緒擴容引起的死迴圈問題

最近在學習併發,看到書上寫到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自己的觀點,有錯誤歡迎指導討論