1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        多線程環(huán)境下,HashMap 為什么會(huì)出現(xiàn)死循環(huán)?

        共 5727字,需瀏覽 12分鐘

         ·

        2021-09-17 09:02

        上一篇:深夜看了張一鳴的微博,讓我越想越后怕

        Java的HashMap是非線程安全的,多線程下應(yīng)該用ConcurrentHashMap。

        多線程下[HashMap]的問題(這里主要說死循環(huán)問題):

        1、為何出現(xiàn)死循環(huán)?

        在多線程下使用非線程安全的HashMap,單線程根本不會(huì)出現(xiàn)。

        • HashMap是采用鏈表解決Hash沖突,因?yàn)槭擎湵斫Y(jié)構(gòu),那么就很容易形成閉合的鏈路,這樣在循環(huán)的時(shí)候只要有線程對(duì)這個(gè)HashMap進(jìn)行g(shù)et操作就會(huì)產(chǎn)生死循環(huán)。
        • 在單線程情況下,只有一個(gè)線程對(duì)HashMap的數(shù)據(jù)結(jié)構(gòu)進(jìn)行操作,是不可能產(chǎn)生閉合的回路的。
        • 那就只有在多線程并發(fā)的情況下才會(huì)出現(xiàn)這種情況,那就是在put操作的時(shí)候,如果size>initialCapacity*loadFactor,那么這時(shí)候HashMap就會(huì)進(jìn)行rehash操作,隨之HashMap的結(jié)構(gòu)就會(huì)發(fā)生翻天覆地的變化。很有可能就是在兩個(gè)線程在這個(gè)時(shí)候同時(shí)觸發(fā)了rehash操作,產(chǎn)生了閉合的回路。

        2、如何產(chǎn)生的?

        存儲(chǔ)數(shù)據(jù)put()

         public V put(K key, V value)
         {
          ......
          //算Hash值
          int hash = hash(key.hashCode());
          int i = indexFor(hash, table.length);
          //如果該key已被插入,則替換掉舊的value (鏈接操作)
          for (Entry<K,V> e = table[i]; e != null; e = e.next) {
           Object k;
           if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
           }
          }
          modCount++;
          //該key不存在,需要增加一個(gè)結(jié)點(diǎn)
          addEntry(hash, key, value, i);
          return null;
         }

        當(dāng)我們往HashMap中put元素的時(shí)候,先根據(jù)key的hash值得到這個(gè)元素在數(shù)組中的位置(即下標(biāo)),然后就可以把這個(gè)元素放到對(duì)應(yīng)的位置中了。

        如果這個(gè)元素所在的位置上已經(jīng)存放有其他元素了,那么在同一個(gè)位子上的元素將以鏈表的形式存放,新加入的元素放在鏈頭,而先前加入的放在鏈尾。

        檢查容量是否超標(biāo)addEntry:

         void addEntry(int hash, K key, V value, int bucketIndex)
         {
          Entry<K,V> e = table[bucketIndex];
          table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
          //查看當(dāng)前的size是否超過了我們?cè)O(shè)定的閾值threshold,如果超過,需要resize
          if (size++ >= threshold)
           resize(2 * table.length);
         }

        如果現(xiàn)在size已經(jīng)超過了threshold,那么就要進(jìn)行resize操作,新建一個(gè)更大尺寸的hash表,然后把數(shù)據(jù)從老的Hash表中遷移到新的Hash表中。

        調(diào)整Hash表大小resize:

         void resize(int newCapacity)
         {
          Entry[] oldTable = table;
          int oldCapacity = oldTable.length;
          ......
          //創(chuàng)建一個(gè)新的Hash Table
          Entry[] newTable = new Entry[newCapacity];
          //將Old Hash Table上的數(shù)據(jù)遷移到New Hash Table上
          transfer(newTable);
          table = newTable;
          threshold = (int)(newCapacity * loadFactor);
         }

        當(dāng)table[]數(shù)組容量較小,容易產(chǎn)生哈希碰撞,所以,Hash表的尺寸和容量非常的重要。

        一般來說,Hash表這個(gè)容器當(dāng)有數(shù)據(jù)要插入時(shí),都會(huì)檢查容量有沒有超過設(shè)定的thredhold,如果超過,需要增大Hash表的尺寸,這個(gè)過程稱為resize。

        多個(gè)線程同時(shí)往HashMap添加新元素時(shí),多次resize會(huì)有一定概率出現(xiàn)死循環(huán),因?yàn)槊看蝦esize需要把舊的數(shù)據(jù)映射到新的哈希表,這一部分代碼在HashMap#transfer() 方法,如下:

         void transfer(Entry[] newTable)
         {
          Entry[] src = table;
          int newCapacity = newTable.length;
          //下面這段代碼的意思是:
          //  從OldTable里摘一個(gè)元素出來,然后放到NewTable中
          for (int j = 0; j < src.length; j++) {
           Entry<K,V> e = src[j];
           if (e != null) {
            src[j] = null;
            do {
             Entry<K,V> next = e.next;//取出第一個(gè)元素
             int i = indexFor(e.hash, newCapacity);
             e.next = newTable[i];
             newTable[i] = e;
             e = next;
            } while (e != null);
           }
          }
         }

        標(biāo)紅代碼是導(dǎo)致多線程使用hashmap出現(xiàn)CUP使用率驟增,出現(xiàn)死循環(huán),從而多個(gè)線程阻塞的罪魁禍?zhǔn)住A硗?,多線程系列面試題和答案全部整理好了,微信搜索互聯(lián)網(wǎng)架構(gòu)師,在后臺(tái)發(fā)送:2T,可以在線閱讀。

        3、圖解HashMap死循環(huán):

        正常的ReHash的過程(單線程):假設(shè)了我們的hash算法就是簡單的用key mod 一下表的大?。ㄒ簿褪菙?shù)組的長度)。

        最上面的是old hash 表,其中的Hash表的size=2, 所以key = 3, 7, 5,在mod 2以后都沖突在table[1]這里了。接下來的三個(gè)步驟是Hash表 resize成4,然后所有的<key,value> 重新rehash的過程。

        并發(fā)下的Rehash(多線程)

        1)假設(shè)我們有兩個(gè)線程。

         do {
          Entry<K,V> next = e.next; // <--假設(shè)線程一執(zhí)行到這里就被調(diào)度掛起了,執(zhí)行其他操作
          int i = indexFor(e.hash, newCapacity);
          e.next = newTable[i];
          newTable[i] = e;
          e = next;
         } while (e != null);

        而我們的線程二執(zhí)行完成了。于是我們有下面的這個(gè)樣子:

        注意,因?yàn)門hread1的 e 指向了key(3),而next指向了key(7),其在線程二rehash后,指向了線程二重組后的鏈表。我們可以看到鏈表的順序被反轉(zhuǎn)后。在這里線程一變成了操作經(jīng)過線程二操作后的HashMap。

        2)線程一被調(diào)度回來執(zhí)行。

        • 先是執(zhí)行 newTalbe[i] = e;
        • 然后是e = next,導(dǎo)致了e指向了key(7),
        • 而下一次循環(huán)的next = e.next導(dǎo)致了next指向了key(3)。

        3)一切安好。

        線程一接著工作。把key(7)摘下來,放到newTable[i]的第一個(gè),然后把e和next往下移。這個(gè)元素所在的位置上已經(jīng)存放有其他元素了,那么在同一個(gè)位子上的元素將以鏈表的形式存放,新加入的放在鏈頭,而先前加入的放在鏈尾。

        4)環(huán)形鏈接出現(xiàn)。

        e.next = newTable[i] 導(dǎo)致  key(3).next 指向了 key(7)

        注意:此時(shí)的key(7).next 已經(jīng)指向了key(3), 環(huán)形鏈表就這樣出現(xiàn)了。

        于是,當(dāng)我們的線程一調(diào)用到,HashTable.get(11)時(shí),悲劇就出現(xiàn)了——Infinite Loop。

        這里介紹了在多線程下為什么HashMap會(huì)出現(xiàn)死循環(huán),不過在真實(shí)的生產(chǎn)環(huán)境下,不會(huì)使用線程不安全的HashMap的。

        原文鏈接:https://blog.csdn.net/dingjianmin/article/details/79780350

        感謝您的閱讀,也歡迎您發(fā)表關(guān)于這篇文章的任何建議,關(guān)注我,技術(shù)不迷茫!小編到你上高速。
            · END ·
        最后,關(guān)注公眾號(hào)互聯(lián)網(wǎng)架構(gòu)師,在后臺(tái)回復(fù):2T,可以獲取我整理的 Java 系列面試題和答案,非常齊全。


        正文結(jié)束


        推薦閱讀 ↓↓↓

        1.不認(rèn)命,從10年流水線工人,到谷歌上班的程序媛,一位湖南妹子的勵(lì)志故事

        2.如何才能成為優(yōu)秀的架構(gòu)師?

        3.從零開始搭建創(chuàng)業(yè)公司后臺(tái)技術(shù)棧

        4.程序員一般可以從什么平臺(tái)接私活?

        5.37歲程序員被裁,120天沒找到工作,無奈去小公司,結(jié)果懵了...

        6.IntelliJ IDEA 2019.3 首個(gè)最新訪問版本發(fā)布,新特性搶先看

        7.這封“領(lǐng)導(dǎo)痛批95后下屬”的郵件,句句扎心!

        8.15張圖看懂瞎忙和高效的區(qū)別!

        一個(gè)人學(xué)習(xí)、工作很迷茫?


        點(diǎn)擊「閱讀原文」加入我們的小圈子!

        瀏覽 29
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            成人在线高清无码 | 9色在线视频 | 99在线精品视频免费观看20 | 捆绑双乳吊起来抽打调教 | 国产自产自拍 | 裸体武打性艳史电影 | 色婷婷丁香五月 | 肏婷婷网 | 国产午夜免费 | 操小妇逼逼bb |