日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

HashMap在java并发中如何发生死循环

發布時間:2023/12/3 编程问答 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 HashMap在java并发中如何发生死循环 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

轉載自? ?HashMap在java并發中如何發生死循環

?

? ?在多線程環境中,使用HashMap進行put操作時會引起死循環,導致CPU使用接近100%,下面通過代碼分析一下為什么會發生死循環。

????? 首先先分析一下HashMap的數據結構:HashMap底層數據結構是有一個鏈表數據構成的,HashMap中定義了一個靜態內部類作為鏈表,代碼如下(與本文無關的代碼省略):

靜態內部類entry代碼?

  • ????static?class?Entry<K,V>?implements?Map.Entry<K,V>?{??
  • ????????final?K?key;??
  • ????????V?value;??
  • ????????Entry<K,V>?next;??
  • ????????final?int?hash;??
  • ??
  • ????????/**??
  • ?????????*?Creates?new?entry.??
  • ?????????*/??
  • ????????Entry(int?h,?K?k,?V?v,?Entry<K,V>?n)?{??
  • ????????????value?=?v;??
  • ????????????next?=?n;??
  • ????????????key?=?k;??
  • ????????????hash?=?h;??
  • ????????}??
  • 、??
  • ????}??
  • ?

    Hashmap屬性代碼?

  • /**??
  • ?*?The?table,?resized?as?necessary.?Length?MUST?Always?be?a?power?of?two.??
  • ?*/??
  • transient?Entry[]?table;??
  • ??? 之所以會導致HashMap出現死循環是因為多線程會導致HashMap的Entry節點形成環鏈,這樣當遍歷集合時Entry的next節點用于不為空,從而形成死循環

    ?

    ??? 單添加元素時會通過key的hash值確認鏈表數組下標

  • public?V?put(K?key,?V?value)?{??
  • ????if?(key?==?null)??
  • ????????return?putForNullKey(value);??
  • ??????
  • ????//確認鏈表數組位置??
  • ????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++;??
  • ????//添加鏈表節點??
  • ????addEntry(hash,?key,?value,?i);??
  • ????return?null;??
  • }??
  • ?

    ?? 下面看一下HashMap添加節點的實現

  • void?addEntry(int?hash,?K?key,?V?value,?int?bucketIndex)?{??
  • ?//bucketIndex?通過key的hash值與鏈表數組的長度計算得出??
  • ????Entry<K,V>?e?=?table[bucketIndex];??
  • ????//創建鏈表節點????????
  • ??table[bucketIndex]?=?new?Entry<K,V>(hash,?key,?value,?e);??
  • ????
  • ??//判斷是否需要擴容??
  • ??if?(size++?>=?threshold)??
  • ????????????resize(2?*?table.length);??
  • }??
  • ?

    ?

    ??? 以上部分的實現不會導致鏈路出現環鏈,環鏈一般會出現HashMap擴容是,下面看看擴容的實現:

  • 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的實現

  • 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);??
  • ????????}??
  • ????}??
  • }??
  • ?? 這個方法的目的是將原鏈表數據的數組拷到新的鏈表數組中,拷貝過程中如果形成環鏈的呢?下面用一個簡單的例子來說明一下:

  • public?class?InfiniteLoop?{??
  • ??
  • ????static?final?Map<Integer,?Integer>?map?=?new?HashMap<Integer,?Integer>(2,?0.75f);??
  • ??
  • ????public?static?void?main(String[]?args)?throws?InterruptedException?{??
  • ??
  • ????????map.put(5,?55);??
  • ??
  • ????????new?Thread("Thread1")?{??
  • ????????????public?void?run()?{??
  • ????????????????map.put(7,?77);??
  • ????????????????System.out.println(map);??
  • ????????????};??
  • ????????}.start();??
  • ??
  • ????????new?Thread("Thread2")?{??
  • ????????????public?void?run()?{??
  • ????????????????map.put(3,?33);??
  • ????????????????System.out.println(map);??
  • ????????????};??
  • ????????}.start();??
  • ??
  • ????}??
  • ??
  • }??
  • ?

    ?

    ?? 下面通過debug跟蹤調試來看看如果導致HashMap形成環鏈,斷點位置:

  • 線程1的put操作
  • 線程2的put操作
  • 線程2的輸出操作
  • HashMap源碼transfer方法中的第一行、第六行、第九行
  • ??? 測試開始

    ??

  • ?使線程1進入transfer方法第一行,此時map的結構如下
  • ??? 2.? 使線程2進入transfer方法第一行,此時map的結構如下:


    ?
    ?3.接著切換回線程1,執行到transfer的第六行,此時map的結構如下:

    4.然后切換回線程2使其執行到transfer方法的第六行,此時map的結夠如上

    5.接著切換回線程1使其執行到transfer方法的第九行,然后切換回線程2使其執行完,此時map的結構如下:


    ?6.切換回線程1執行循環,因為線程1之前是停在HashMap的transfer方法的第九行處,所以此時transfer方法的節點e的key=3,e.next的key=7

  • 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);//線程1等線程2執行結束后??
  • ??????????????????????????????????????????????????????????//從此處開始執行??
  • ??????????????????????????????????????????????????????????//此時e的key=3,e.next.key=7??
  • ??????????????????????????????????????????????????????????//但是此時的e.next.next的key=3了??
  • ??????????????????????????????????????????????????????????//(被線程2修改了)??
  • ????????????????????e.next?=?newTable[i];??
  • ????????????????????newTable[i]?=?e;??
  • ????????????????????e?=?next;??
  • ????????????????}?while?(e?!=?null);??
  • ????????????}??
  • ????????}??
  • }??
  • ??

    ? 下面線程1開始執行第一次循環,循環后的map結構如下:


    ?

    接著執行第二次循環:e.key=7,e.next.key=3,e.next.next=null

    接著執行第三次循環,從而導致環鏈形成,map結構如下


    ?并且此時的map中還丟失了key=5的節點

    ?

    總結

    以上是生活随笔為你收集整理的HashMap在java并发中如何发生死循环的全部內容,希望文章能夠幫你解決所遇到的問題。

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