HashMap多線程操作下的問題總結

HashMap多線程操作下的問題總結

前段時間海外庫存系統隔一段時間就會出現CPU使用率告警。最終排查出來,是由於海外庫存在接收多線程數據查詢結果時,使用了一個普通的HashMap來接收,也就是多個線程對同一個HashMap進行非線程安全的put操作導致的。經證實,海外庫存的數據查詢偶爾出現非預期結果,也與此有關:比如有庫存的商品,查出來卻是0等等。
HashMap多線程操作會造成一系列問題,這很多人都知道。但反過來根據現象查問題,可能就不那麼明顯了。因此這裏對多線程下HashMap使用會造成的問題做個小總結,以供大家“根據現象反查問題”作參考。

問題1. 導致死循環,CPU使用率飆升

特徵1:生產環境某個實例CPU使用率飆升,並且多次thread dump顯示同一個線程在很長一段時間內一直在對同一個HashMap在做put、get、或者遍歷操作。

特徵2:每當一個線程進入死循環,就會佔用100%/CPU核數 的CPU利用率,我們的生產環境是4核CPU,因此可以看 到,生產環境上有25%左右、50%左右、75%左右以及99%的CPU利用率(99%利用率的時候會告警,我們就去重啓了,所以這裏看不到)。

下圖左邊是 修復HashMap多線程使用前,日常的CPU利用率,右圖是修復後一週的日常CPU利用率。
這裏寫圖片描述

大致原理

HashMap的內部存儲結構如下,由一個數組,以及數組上的鏈表組成:
這裏寫圖片描述

其中,key的Hash值與數組長度取模得出的值相等的元素將會放在同一個鏈表上。而HashMap查找的過程,實際就是根據key計算hash值,與當前長度取模,從而定位到數組中的某個鏈表,然後再從這個鏈表上進行遍歷查找數據。

在put的過程中,隨着hashmap元素個數的增長,鏈表越來越長,Map查找的效率會越來越低。因此當數量增長到一定時候,一般是爲 元素個數 > 數組length * loadFactor。loadFactor默認0.75,可以自己定義。就會對數組進行擴容,並且遍歷原來的HashMap中的所有元素,將原有元素全部重新put到新的數組及鏈表中。

在多線程情況下,put的過程在操作同一個鏈表時,會形成如下循環鏈表(這裏要講篇幅就長了,網上關於HahsMap擴容過程的資料很多)。當進行get查詢,或者遍歷操作的時候,就會進行鏈表的死循環遍歷,從而導致CPU佔用彪高。
這裏寫圖片描述

問題2. Map.size()與實際不合

多線程環境下put的HashMap會被“損壞”,其中會造成size與實際不符合,以下代碼中,如果有幸沒有進入死循環,assert斷言有很大概率不會通過。

Map<String, Object> testMap = new HashMap<>();
// TODO 多線程環境下對testMap進行put操作 ...
// 。。。 。。。
// 多線程對testMap進行put操作完成

int realSize = 0;
for (Entry entry : testMap.entrySet()){
    realSize += 1;
}
// assert 很大概率失敗
assert realSize == testMap.size();

Map.size與實際不合將會導致一些依賴Map.size的業務邏輯出現不可預知的異常。

問題3. 數據丟失

多線程環境下put的HashMap會造成數據丟失,明明put進去的數據,卻get不到了。
下邊的一段代碼中,如果運氣好,沒有進入死循環,那麼assert斷言也有很大可能性過不了。

Map<Integer, Object> testMap = new HashMap<>();
Object val = new Object();
CountDownLatch cdl = new CountDownLatch(100);
for (int i = 0; i < 100 ; i++) {
    new Thread(new Runnable() {
        @Override
        public void run() {
            for (int j = i * 100; j < (i + 1) * 100 ; j++ ) {
                testMap.put(j, val);
            }
            cdl.countDown();
        }
    }).start();
}
cdl.await();
for (int i = 0; i < 10000; i++ ) {
    // assert 很大概率會失敗
    assert testMap.get(i) != null;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章