<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          多線程環(huán)境下,HashMap 為什么會出現(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,單線程根本不會出現(xiàn)。

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

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

          存儲數(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不存在,需要增加一個結(jié)點
            addEntry(hash, key, value, i);
            return null;
           }

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

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

          檢查容量是否超標(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是否超過了我們設(shè)定的閾值threshold,如果超過,需要resize
            if (size++ >= threshold)
             resize(2 * table.length);
           }

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

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

           void resize(int newCapacity)
           {
            Entry[] oldTable = table;
            int oldCapacity = oldTable.length;
            ......
            //創(chuàng)建一個新的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表這個容器當(dāng)有數(shù)據(jù)要插入時,都會檢查容量有沒有超過設(shè)定的thredhold,如果超過,需要增大Hash表的尺寸,這個過程稱為resize。

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

           void transfer(Entry[] newTable)
           {
            Entry[] src = table;
            int newCapacity = newTable.length;
            //下面這段代碼的意思是:
            //  從OldTable里摘一個元素出來,然后放到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;//取出第一個元素
               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),從而多個線程阻塞的罪魁禍?zhǔn)?。另外,多線程系列面試題和答案全部整理好了,微信搜索互聯(lián)網(wǎng)架構(gòu)師,在后臺發(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]這里了。接下來的三個步驟是Hash表 resize成4,然后所有的<key,value> 重新rehash的過程。

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

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

           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í)行完成了。于是我們有下面的這個樣子:

          注意,因為Thread1的 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]的第一個,然后把e和next往下移。這個元素所在的位置上已經(jīng)存放有其他元素了,那么在同一個位子上的元素將以鏈表的形式存放,新加入的放在鏈頭,而先前加入的放在鏈尾。

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

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

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

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

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

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

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


          正文結(jié)束


          推薦閱讀 ↓↓↓

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

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

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

          4.程序員一般可以從什么平臺接私活?

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

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

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

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

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


          點擊「閱讀原文」加入我們的小圈子!

          瀏覽 29
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲日本淫色无码视频 | 先锋影音男人在线资源站 | 青青草国产免费无码欧美 | 干屄网站 | 玖玖婷婷五月天 |