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

歡迎訪問 生活随笔!

生活随笔

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

编程问答

高并发下的 HashMap 为什么会死循环

發布時間:2024/8/23 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 高并发下的 HashMap 为什么会死循环 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

作者 |?tech-bus.七十一

來源 |?程序員巴士

前言

??HashMap并發情況下產生的死循環問題在JDK 1.7及之前版本是存在的,JDK 1.8 通過增加loHead頭節點和loTail尾節點進行了修復,雖然進行了修復,但是如果涉及到并發情況下需要使用hash表,建議使用CurrentHashMap替代HashMap來確保不會出現線程安全問題。

??JDK 1.7及之前 HashMap在并發情況下產生的循環問題,將可能致使服務器的CPU飆升至100%,那究竟是如何造成的呢,為了解答這個疑惑,今天就帶大家來了解一下線程不安全的HashMap在高并發的情況下是如何造成死循環的,要探究HashMap死循環的原因首先要從HashMap的源碼開始進行分析,這樣才能從根本上對HashMap進行理解,從而了解問題產生的原因。在分析之前我們要知道在JDK 1.7版本及之前HashMap采用的是數組 + 鏈表的數據結構,而在JDK 1.8則是采用數組 + 鏈表 + 紅黑樹的數據結構以進一步降低hash沖突后帶來的查詢損耗。

正文

一切還得從元素插入說起,HashMap進行元素的插入,這里會調用put()方法

public?V?put(K?key,?V?value)?{if?(table?==?EMPTY_TABLE)?{inflateTable(threshold);//分配數組空間}if?(key?==?null)return?putForNullKey(value);int?hash?=?hash(key);//對key的hashcode進一步計算,確保散列均勻int?i?=?indexFor(hash,?table.length);//獲取在table中的實際位置for?(Entry<K,V>?e?=?table[i];?e?!=?null;?e?=?e.next)?{...}modCount++;//保證并發訪問時,若HashMap內部結構發生變化,快速響應失敗//重點關注這個addEntry增加元素的方法addEntry(hash,?key,?value,?i);return?null;}

緊接著我們來看這個addEntry()方法,里面調用的resize()擴容方法是今天的主角

void?addEntry(int?hash,?K?key,?V?value,?int?bucketIndex)?{if?((size?>=?threshold)?&&?(null?!=?table[bucketIndex]))?{resize(2?*?table.length);//當size超過臨界閾值threshold,并且即將發生哈希沖突時進行擴容,擴容后新容量為舊容量的2倍hash?=?(null?!=?key)???hash(key)?:?0;bucketIndex?=?indexFor(hash,?table.length);//擴容后重新計算插入的位置下標}//把元素放入HashMap的桶的對應位置createEntry(hash,?key,?value,?bucketIndex);}

下面我們進入到resize()法中,再揭開里面的transfer()方法的面紗,這個方法也是造成死循環的罪魁禍首

//按新的容量擴容Hash表??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,?initHashSeedAsNeeded(newCapacity));table?=?newTable;//修改HashMap的底層數組??threshold?=?(int)Math.min(newCapacity?*?loadFactor,?MAXIMUM_CAPACITY?+?1);//修改閥值??}

最后一起來仔細分析這個transfer()方法

//將老的表中的數據拷貝到新的結構中??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;??if?(rehash)?{//如果是重新Hash,則需要重新計算hash值??e.hash?=?null?==?e.key???0?:?hash(e.key);??}??int?i?=?indexFor(e.hash,?newCapacity);//定位Hash桶??e.next?=?newTable[i];//元素連接到桶中,這里相當于單鏈表的插入,總是插入在最前面newTable[i]?=?e;//newTable[i]的值總是最新插入的值e?=?next;//繼續下一個元素??}??}??}

添加元素達到閥值后對HashMap進行擴容,走reaize()方法,在對HashMap進行擴容時,又會調用一個transfer()對舊的HashMap中的元素進行轉移,那么我們今天要探究的死循環問題 就是發生在這個方法里的,在進行元素轉移時transfer()方法里會調用下面四行代碼

Entry<K,V>?next?=?e.next;? e.next?=?newTable[i]; newTable[i]?=?e; e?=?next;

把元素插入新的HashMap中,粗略的看下這四行代碼似乎并沒有什么問題,元素進行轉移的圖如下(線程不沖突的情況下)

那么我們讓線程A、B同時訪問我這段代碼,當現A線程執行到以下代碼時

Entry<k,v>?next?=?e.next;

線程A交出時間片,線程B這時候接手轉移并且完成了元素的轉移,這個時候線程A又拿到時間片并接著執行后續的代碼

執行后代碼如圖,當e = a時,這時候這時候再執行

e.next?=?newTable[i];//?a元素指向了b元素,產生了循環

??在鏈表產生了循環后,當get()方法獲取元素的時候正好落在這個循環的鏈表上時,線程會一直在環里遍歷,無法跳出,從而導致CPU飆升至100%!

總結

在多線程情況下盡量不要用HashMap,可以用線程安全的hash表來代替,例如使用下面這些

oncurrentHashMap、HashTable、Collections.synchronizedMap() 來避免產生多線程安全問題。

往期推薦

云計算到底是誰發明的?

長跑11年,騰訊開源的變與不變

低代碼發展專訪系列之一:低代碼平臺產品的使用者都是誰?

內容整理志愿者招募了!

點分享

點收藏

點點贊

點在看

總結

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

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