日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

并发的HashMap为什么会引起死循环?(转)

發布時間:2025/7/25 44 豆豆
生活随笔 收集整理的這篇文章主要介紹了 并发的HashMap为什么会引起死循环?(转) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

本文轉自http://blog.csdn.net/zhuqiuhui/article/details/51849692

今天研讀Java并發容器和框架時,看到為什么要使用ConcurrentHashMap時,其中有一個原因是:線程不安全的HashMap, HashMap在并發執行put操作時會引起死循環,是因為多線程會導致HashMap的Entry鏈表形成環形數據結構,查找時會陷入死循環。糾起原因看了其他的博客,都比較抽象,所以這里以圖形的方式展示一下,希望支持!

?

(1)當往HashMap中添加元素時,會引起HashMap容器的擴容,原理不再解釋,直接附源代碼,如下:

[java]?view plaincopy
  • /**??
  • ????*??
  • ????*?往表中添加元素,如果插入元素之后,表長度不夠,便會調用resize方法擴容??
  • ????*/????
  • ???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);????
  • ???????if?(size++?>=?threshold)????
  • ???????????resize(2?*?table.length);????
  • ???}????
  • ????
  • ???/**??
  • ????*?resize()方法如下,重要的是transfer方法,把舊表中的元素添加到新表中?
  • ????*/????
  • ???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];????
  • ???????transfer(newTable);????
  • ???????table?=?newTable;????
  • ???????threshold?=?(int)(newCapacity?*?loadFactor);????
  • ???}????
  • (2)參考上面的代碼,便引入到了transfer方法,(引入重點)這就是HashMap并發時,會引起死循環的根本原因所在,下面結合transfer的源代碼,說明一下產生死循環的原理,先列transfer代碼(這是里JDK7的源偌),如下:

    [java]?view plaincopy
  • /**?
  • ?????*?Transfers?all?entries?from?current?table?to?newTable.?
  • ?????*/??
  • ????void?transfer(Entry[]?newTable,?boolean?rehash)?{??
  • ????????int?newCapacity?=?newTable.length;??
  • ????????for?(Entry<K,V>?e?:?table)?{??
  • ??
  • ????????????while(null?!=?e)?{??
  • ????????????????Entry<K,V>?next?=?e.next;????????????---------------------(1)??
  • ????????????????if?(rehash)?{??
  • ????????????????????e.hash?=?null?==?e.key???0?:?hash(e.key);??
  • ????????????????}??
  • ????????????????int?i?=?indexFor(e.hash,?newCapacity);???
  • ????????????????e.next?=?newTable[i];??
  • ????????????????newTable[i]?=?e;??
  • ????????????????e?=?next;??
  • ????????????}?//?while??
  • ??
  • ????????}??
  • ????}??
  • ?

    ?

    (3)假設:

    [java]?view plaincopy
  • Map<Integer>?map?=?new?HashMap<Integer>(2);??//?只能放置兩個元素,其中的threshold為1(表中只填充一個元素時),即插入元素為1時就擴容(由addEntry方法中得知)??
  • //放置2個元素?3?和?7,若要再放置元素8(經hash映射后不等于1)時,會引起擴容??
  • 假設放置結果圖如下:

    ? ? ?

    ?現在有兩個線程A和B,都要執行put操作,即向表中添加元素,即線程A和線程B都會看到上面圖的狀態快照

    執行順序如下:

    ? ? ? ? ? ? ? ?執行一: ?線程A執行到transfer函數中(1)處掛起(transfer函數代碼中有標注)。此時在線程A的棧中

    ?

    [java]?view plaincopy
  • e?=?3??
  • next?=?7??
  • ?

    ?

    ? ? ? ? ? ? ? 執行二:線程B執行 transfer函數中的while循環,即會把原來的table變成新一table(線程B自己的棧中),再寫入到內存中。如下圖(假設兩個元素在新的hash函數下也會映射到同一個位置)

    ? ? ? ? ? ? ?執行三: 線程A解掛,接著執行(看到的仍是舊表),即從transfer代碼(1)處接著執行,當前的 e = 3, next = 7, 上面已經描述。

    ? ? ? ? ? ? ? ? ? ? ? ? ??

    ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?1. 處理元素 3 , 將 3 放入 線程A自己棧的新table中(新table是處于線程A自己棧中,是線程私有的,不肥線程2的影響),處理3后的圖如下:

    ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 2. ?線程A再復制元素 7 ,當前 e = 7 ,而next值由于線程 B 修改了它的引用,所以next 為 3 ,處理后的新表如下圖

    ?

    ? ? ? ? ? ? ? ? ? ? ? ? ? ?3. 由于上面取到的next = 3, 接著while循環,即當前處理的結點為3, next就為null ,退出while循環,執行完while循環后,新表中的內容如下圖:

    ?

    ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 4. 當操作完成,執行查找時,會陷入死循環!

    ?

    ?

    下文轉自https://www.cnblogs.com/yjbjingcha/p/6957909.html

    HashMap在高并發下引起的死循環

    HashMap事實上并非線程安全的,在高并發的情況下,是非常可能發生死循環的,由此造成CPU 100%,這是非常可怕的。所以在多線程的情況下,用HashMap是非常不妥當的行為,應採用線程安全類ConcurrentHashMap進行取代。

    ?

    ?

    HashMap死循環原因

    ?

    HashMap進行存儲時,假設size超過當前最大容量*負載因子時候會發生resize。首先看一下resize原代碼

    ?

    ?

    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];transfer(newTable);table = newTable;threshold = (int)(newCapacity * loadFactor);}
    而這段代碼中又調用了transfer()方法,而這種方法實現的機制就是將每一個鏈表轉化到新鏈表,而且鏈表中的位置發生反轉,而這在多線程情況下是非常easy造成鏈表回路。從而發生get()死循環。我們看一下他的源碼

    ?

    ?

    void transfer(Entry[] newTable) {Entry[] src = table;int newCapacity = newTable.length;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);}}}

    ?

    ?

    HashMap死循環演示

    假如有兩個線程P1、P2,以及鏈表 a=》b=》null

    ?

    1、P1先運行,運行完"Entry<K,V> next = e.next;"代碼后發生堵塞,或者其它情況不再運行下去,此時e=a。next=b

    2、而P2已經運行完整段代碼,于是當前的新鏈表newTable[i]為b=》a=》null

    3、P1又繼續運行"Entry<K,V> next = e.next;"之后的代碼,則運行完"e=next;"后,newTable[i]為a《=》b。則造成回路,while(e!=null)一直死循環

    ?

    ?

    總結

    HashMap并不是線程安全,所以在多線程情況下,應該首先考慮用ConcurrentHashMap。避免悲劇的發生

    ?

    ?

    轉載于:https://www.cnblogs.com/panxuejun/p/8577410.html

    總結

    以上是生活随笔為你收集整理的并发的HashMap为什么会引起死循环?(转)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。