JDK1.8多線程使用HashMap丟數據原因分析

今天突然發(fā)現線上對 Kafka 消費者的監(jiān)控出了問題,經過排查發(fā)現是在多線程的情況下使用了 HashMap 進行讀寫,下面來詳細分析一下丟數據的原因。

首先寫一個 demo ,模擬線上問題

public class Main {

    private static Map<Integer, String> map = new HashMap<>(32);

    static ExecutorService exec = Executors.newFixedThreadPool(10);

    public static void main(String[] args) throws InterruptedException {
        for (int i = 1; i <= 10; i++) {
            int finalI = i;
            exec.submit(() -> {
                map.put(finalI, "test:" + finalI);
            });
        }
        TimeUnit.SECONDS.sleep(1);
        System.out.println(map);
    }
    
}

運行幾次后會發(fā)現打印出了不正常的結果

{2=test:2, 3=test:3, 4=test:4, 5=test:5, 6=test:6, 7=test:7, 8=test:8, 9=test:9, 10=test:10}

我在初始化時設置初始容量為32,所以不會有擴容的問題,下面我們從源碼找一找問題,首先我看一下 table 這個字段的解釋

    /**
     * The table, initialized on first use, and resized as
     * necessary. When allocated, length is always a power of two.
     * (We also tolerate length zero in some operations to allow
     * bootstrapping mechanics that are currently not needed.)
     */
    transient Node<K,V>[] table;

大體意思就是在第一次使用時進行初始化,然后會進行 resize ,下面看一下 put 調用的 putVal 中的幾行代碼

if ((tab = table) == null || (n = tab.length) == 0)
    n = (tab = resize()).length;

從這里我們可以發(fā)現在第一次調用 put 時會調用 resize ,下面看一下resize中的部分代碼

    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
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        if (oldTab != null) {
                // 一些復制操作
        }
        return newTab;

可以看出第一次調用 put 時會走到else中,對容量和閾值進行初始化,初始化完畢后我們可以看到,返回了一個新的table,這里在多線程環(huán)境下就會導致數據丟失的問題,所以如果我們要在多線程環(huán)境下使用map的話,還是推薦使用 ConcurrentHashMap 的。

?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • 進程和線程 進程 所有運行中的任務通常對應一個進程,當一個程序進入內存運行時,即變成一個進程.進程是處于運行過程中...
    勝浩_ae28閱讀 5,256評論 0 23
  • 摘要 HashMap是Java程序員使用頻率最高的用于映射(鍵值對)處理的數據類型。隨著JDK(Java Deve...
    周二倩你一生閱讀 1,355評論 0 5
  • 本文轉自 https://zhuanlan.zhihu.com/p/21673805 美團點評技術團隊· 3 個月...
    抓兔子的貓閱讀 1,150評論 0 1
  • 放學時,班主任說如果一個人說話整個小組留下。很不幸,我們組中招了。我一向挺喜歡這個班主任,可是對于她的制度,我不能...
    木顏初槿閱讀 300評論 0 0
  • 警戒自己丟掉善良的心,對社會刻薄堅硬。 警戒自己忘記他的無奈和付出,無理取鬧。 警戒自己放棄自己,每天家居服素顏邋...
    白勿閱讀 324評論 0 0

友情鏈接更多精彩內容