生活随笔
收集整理的這篇文章主要介紹了
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并发中如何发生死循环的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。