最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

淺談HashMap在高并發(fā)下的問題

 更新時間:2021年07月31日 12:01:07   作者:梧桐和風  
這篇文章主要介紹了HashMap在高并發(fā)下的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教

前言

總所周知,HashMap不是線程安全的,在高并發(fā)情況下會出現(xiàn)問題。特別是,在java1.7中,多線程的HashMap會出現(xiàn)CPU 100%的嚴重問題。這個問題是怎樣產(chǎn)生的,后續(xù)版本還會有這個問題嗎(指java8及后續(xù)版本)?下面就來用通俗的語言講解下。

解析

關(guān)于這個問題,是由于java7多線程擴容機制下鏈表變?yōu)檠h(huán)鏈表,再獲取該鏈表導(dǎo)致的。

看下java7中擴容的代碼。java7中HashMap的實現(xiàn)為數(shù)組+鏈表的形式,沒有紅黑樹。

java7擴容的原則很簡單,新數(shù)組長度為原數(shù)組2倍。遍歷原數(shù)組,將數(shù)組每個位置(有可能為空,有可能只有一個數(shù)組,有可能是一個鏈表)重新哈希,放到對應(yīng)的新數(shù)組上。全部遍歷完后更改數(shù)組指針,指向新數(shù)組。需要注意的是,這里重哈希將鏈表元素放到新數(shù)組,使用的是頭插法。

 // 擴容核心方法,基本思想就是遍歷數(shù)據(jù),使用頭插法將舊數(shù)組元素移到新數(shù)組。
 void transfer(Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;
        // 遍歷舊數(shù)組
        for (Entry<K,V> e : table) {
            // 元素不為空。遍歷該位置鏈表
            while(null != e) {
                Entry<K,V> next = e.next;
                if (rehash) {
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                int i = indexFor(e.hash, newCapacity);
                e.next = newTable[i]; // 頭插法,新節(jié)點next指向該位置首節(jié)點
                newTable[i] = e; // 新元素歸位
                e = next; // 指向下一個節(jié)點,繼續(xù)遍歷
            }
        }
    }
   void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }
        Entry[] newTable = new Entry[newCapacity]; // 創(chuàng)建新數(shù)組
        transfer(newTable, initHashSeedAsNeeded(newCapacity)); // 擴容
        table = newTable;  // 更改指針
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    }

這里處理的話,如果單線程情況下不會有問題。如果在多線程情況下,會導(dǎo)致鏈表在擴容過程中形成循環(huán)鏈表。

形成循環(huán)鏈表的原因在于多線程和頭插法。試想,兩個線程在添加元素時,同時發(fā)現(xiàn)該擴容了,然后同時發(fā)起擴容過程。由上述代碼可知,擴容完成之前是在自己的線程里創(chuàng)建一個新數(shù)組。等擴容完成后(也就是將原數(shù)組元素遷移到新數(shù)組后)再更改指針指向新擴容數(shù)組。

舉例初始HashMap是這樣的

在這里插入圖片描述

假設(shè)兩個線程同時擴容,一個線程擴容到一半后被掛起。(標識了某鏈表的e和next),另一個線程執(zhí)行擴容,且完成了擴容。

紅色的數(shù)組和元素表示線程1,也就是擴容一半掛起的線程,而線程二已完成擴容。觀察完成擴容的線程二,在3的位置,該鏈表的位置順序已經(jīng)改變(原數(shù)組順訊為3->7,現(xiàn)在反過來了,這是使用頭插法的效果,你也可以對著代碼試試)。從圖中也可以看出,線程1,2分別創(chuàng)建了自己的新數(shù)組,并在自己的新數(shù)組中完成擴容。

這時線程1開始執(zhí)行。熟悉下它即將執(zhí)行的代碼。

       // transfer 方法循環(huán)部分
         while(null != e) {
                Entry<K,V> next = e.next;
                if (rehash) {
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                int i = indexFor(e.hash, newCapacity);
                e.next = newTable[i]; // 頭插法,新節(jié)點next指向該位置首節(jié)點
                newTable[i] = e; // 新元素歸位
                e = next; // 指向下一個節(jié)點,繼續(xù)遍歷
            }

下面線程1將使用頭插法將元素插入線程1新建的數(shù)組中去。注意此時e指向的是Key3,next指向的是Key7。不用想也知道后面操作會有問題。因為現(xiàn)在的next指針指的不是e的下一個元素,而是它的前一個元素!

如果繼續(xù)走代碼的話,把Key3(當前e指向元素)放入新數(shù)組后,再把Key7放入新數(shù)組,后面會放哪個元素?當然又是Key3了,因為Key7next是Key3,這樣就形成了死循環(huán)。

在這里插入圖片描述

java8的改進

  • 添加了紅黑樹,當鏈表長度大于8時,會將鏈表轉(zhuǎn)為紅黑樹。
  • 擴容后,新數(shù)組中的鏈表順序依然與舊數(shù)組中的鏈表順序保持一致。具體JDK8是用 head 和 tail 來保證鏈表的順序和之前一樣,這樣就不會產(chǎn)生循環(huán)引用。也就沒有死循環(huán)了。
  • 雖然修復(fù)了死循環(huán)的BUG,但是HashMap 還是非線程安全類,仍然會產(chǎn)生數(shù)據(jù)丟失等問題。

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • 解決springboot與springcloud版本兼容問題(附版本兼容表)

    解決springboot與springcloud版本兼容問題(附版本兼容表)

    在基于spring boot搭建spring cloud時,創(chuàng)建eureka后啟動服務(wù)發(fā)生報錯,本文給大家介紹了解決springboot與springcloud版本兼容問題的幾種方案,需要的朋友可以參考下
    2024-02-02
  • Spring實戰(zhàn)之獲取方法返回值操作示例

    Spring實戰(zhàn)之獲取方法返回值操作示例

    這篇文章主要介紹了Spring實戰(zhàn)之獲取方法返回值操作,涉及spring配置文件與方法返回值操作相關(guān)使用技巧,需要的朋友可以參考下
    2019-12-12
  • Java中Properties類的操作實例詳解

    Java中Properties類的操作實例詳解

    這篇文章主要介紹了Java中Properties類的操作實例詳解的相關(guān)資料,需要的朋友可以參考下
    2017-04-04
  • SpringBoot+ENC實現(xiàn)密鑰加密的使用示例

    SpringBoot+ENC實現(xiàn)密鑰加密的使用示例

    本文主要介紹了SpringBoot+ENC實現(xiàn)密鑰加密的使用示例,主要是為了將配置信息從應(yīng)用程序代碼中分離出來,以提高安全性和可維護性,感興趣的可以了解一下
    2024-07-07
  • java charAt()返回數(shù)值型的使用示例

    java charAt()返回數(shù)值型的使用示例

    本文主要介紹了java charAt()返回數(shù)值型的使用示例,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-11-11
  • JDK21無法導(dǎo)入TimeUnit類的解決辦法

    JDK21無法導(dǎo)入TimeUnit類的解決辦法

    這篇文章主要給大家介紹了關(guān)于JDK21無法導(dǎo)入TimeUnit類的解決辦法,TimeUnit是java.util.concurrent包下面的一個類,TimeUnit提供了可讀性更好的線程暫停操作,通常用來替換Thread.sleep(),需要的朋友可以參考下
    2024-01-01
  • java streamfilter list 過濾的實現(xiàn)

    java streamfilter list 過濾的實現(xiàn)

    Java Stream API中的filter方法是過濾List集合中元素的一個強大工具,可以輕松地根據(jù)自定義條件篩選出符合要求的元素,本文就來介紹一下java streamfilter list 過濾的實現(xiàn),感興趣的可以了解一下
    2025-03-03
  • Java集合Iterator迭代的實現(xiàn)方法

    Java集合Iterator迭代的實現(xiàn)方法

    這篇文章主要介紹了Java集合Iterator迭代接口的實現(xiàn)方法,非常不錯,具有參考借鑒家,對Java 結(jié)合iterator知識感興趣的朋友一起看看吧
    2016-08-08
  • Java中遍歷數(shù)組使用foreach循環(huán)還是for循環(huán)?

    Java中遍歷數(shù)組使用foreach循環(huán)還是for循環(huán)?

    這篇文章主要介紹了Java中遍歷數(shù)組使用foreach循環(huán)還是for循環(huán)?本文著重講解for語句的語法并給出使用實例,同時總結(jié)出盡量使用foreach語句遍歷數(shù)組,需要的朋友可以參考下
    2015-06-06
  • SpringBoot+Mybatis Plus導(dǎo)致PageHelper失效的解決方法

    SpringBoot+Mybatis Plus導(dǎo)致PageHelper失效的解決方法

    在Springboot項目中使用分頁插件的時候,發(fā)現(xiàn)PageHelper插件失效了 ,本文主要介紹了SpringBoot+Mybatis Plus導(dǎo)致PageHelper失效的解決方法,感興趣的可以了解一下
    2024-07-07

最新評論

荥阳市| 巴林左旗| 宜兰市| 噶尔县| 颍上县| 米易县| 铁岭县| 浦北县| 金塔县| 图片| 溧阳市| 辽阳市| 五台县| 阿拉善右旗| 翁牛特旗| 即墨市| 蓬安县| 屏东县| 黔东| 施秉县| 瑞昌市| 翁牛特旗| 美姑县| 那坡县| 兴隆县| 岳池县| 澄迈县| 昌图县| 清镇市| 南漳县| 尤溪县| 乌恰县| 黎川县| 洪雅县| 介休市| 图木舒克市| 佛学| 华阴市| 沁水县| 赤水市| 石林|