一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - 一次因HashSet引起的并發問題詳解

一次因HashSet引起的并發問題詳解

2021-06-10 15:27crossoverjie Java教程

這篇文章主要給大家介紹了一次因HashSet引起的并發問題的解決方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

為啥要用hahset?

假如我們現在想要在一大堆數據中查找x數據。linkedlist的數據結構就不說了,查找效率低的可怕。arraylist哪,如果我們不知道x的位置序號,還是一樣要全部遍歷一次直到查到結果,效率一樣可怕。hashset天生就是為了提高查找效率的。

背景

上午剛到公司,準備開始一天的摸魚之旅時突然收到了一封監控中心的郵件。

心中暗道不好,因為監控系統從來不會告訴我應用完美無 bug,其實系統挺猥瑣。

打開郵件一看,果然告知我有一個應用的線程池隊列達到閾值觸發了報警。

由于這個應用出問題非常影響用戶體驗;于是立馬讓運維保留現場 dump 線程和內存同時重啟應用,還好重啟之后恢復正常。于是開始著手排查問題。

分析

首先了解下這個應用大概是做什么的。

簡單來說就是從 mq 中取出數據然后丟到后面的業務線程池中做具體的業務處理。

而報警的隊列正好就是這個線程池的隊列。

跟蹤代碼發現構建線程池的方式如下:

?
1
2
3
4
threadpoolexecutor executor = new threadpoolexecutor(coresize, maxsize,
  0l, timeunit.milliseconds,
  new linkedblockingqueue<runnable>());;
  put(poolname,executor);

采用的是默認的 linkedblockingqueue 并沒有指定大小(這也是個坑),于是這個隊列的默認大小為 integer.max_value。

由于應用已經重啟,只能從僅存的線程快照和內存快照進行分析。

內存分析

先利用 mat 分析了內存,的到了如下報告。

一次因HashSet引起的并發問題詳解

其中有兩個比較大的對象,一個就是之前線程池存放任務的 linkedblockingqueue,還有一個則是 hashset。

當然其中隊列占用了大量的內存,所以優先查看,hashset 一會兒再看。

由于隊列的大小給的夠大,所以結合目前的情況來看應當是線程池里的任務處理較慢,導致隊列的任務越堆越多,至少這是目前可以得出的結論。

線程分析

再來看看線程的分析,這里利用 fastthread.io  這個網站進行線程分析。

因為從表現來看線程池里的任務遲遲沒有執行完畢,所以主要看看它們在干嘛。

正好他們都處于 runnable 狀態,同時堆棧如下:

一次因HashSet引起的并發問題詳解

發現正好就是在處理上文提到的 hashset,看這個堆棧是在查詢 key 是否存在。通過查看 312 行的業務代碼確實也是如此。

這里的線程名字也是個坑,讓我找了好久。

定位

分析了內存和線程的堆棧之后其實已經大概猜出一些問題了。

這里其實有一個前提忘記講到:

這個告警是凌晨三點發出的郵件,但并沒有電話提醒之類的,所以大家都不知道。

到了早上上班時才發現并立即 dump 了上面的證據。

所有有一個很重要的事實:這幾個業務線程在查詢 hashset 的時候運行了 6 7 個小時都沒有返回。

通過之前的監控曲線圖也可以看出:

一次因HashSet引起的并發問題詳解

操作系統在之前一直處于高負載中,直到我們早上看到報警重啟之后才降低。

同時發現這個應用生產上運行的是 jdk1.7 ,所以我初步認為應該是在查詢 key 的時候進入了 hashmap 的環形鏈表導致 cpu 高負載同時也進入了死循環。

為了驗證這個問題再次 review 了代碼。

整理之后的偽代碼如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//線程池
private executorservice executor;
private set<string> set = new hashset();
private void execute(){
 
 while(true){
 //從 mq 中獲取數據
 string key = submq();
 executor.excute(new worker(key)) ;
 }
}
public class worker extends thread{
 private string key ;
 public worker(string key){
 this.key = key;
 }
 @override
 private void run(){
 if(!set.contains(key)){
 //數據庫查詢
 if(querydb(key)){
 set.add(key);
 return;
 }
 }
 //達到某種條件時清空 set
 if(flag){
 set = null ;
 }
 }
}

大致的流程如下:

  • 源源不斷的從 mq 中獲取數據。
  • 將數據丟到業務線程池中。
  • 判斷數據是否已經寫入了 set。
  • 沒有則查詢數據庫。
  • 之后寫入到 set 中。

這里有一個很明顯的問題,那就是作為共享資源的 set 并沒有做任何的同步處理。

這里會有多個線程并發的操作,由于 hashset 其實本質上就是 hashmap,所以它肯定是線程不安全的,所以會出現兩個問題:

  • set 中的數據在并發寫入時被覆蓋導致數據不準確。
  • 會在擴容的時候形成環形鏈表。

第一個問題相對于第二個還能接受。

通過上文的內存分析我們已經知道這個 set 中的數據已經不少了。同時由于初始化時并沒有指定大小,僅僅只是默認值,所以在大量的并發寫入時候會導致頻繁的擴容,而在 1.7 的條件下又可能會形成環形鏈表。

不巧的是代碼中也有查詢操作(contains()),觀察上文的堆棧情況:

一次因HashSet引起的并發問題詳解

發現是運行在 hashmap 的 465 行,來看看 1.7 中那里具體在做什么:

一次因HashSet引起的并發問題詳解

已經很明顯了。這里在遍歷鏈表,同時由于形成了環形鏈表導致這個 e.next 永遠不為空,所以這個循環也不會退出了。

到這里其實已經找到問題了,但還有一個疑問是為什么線程池里的任務隊列會越堆越多。我第一直覺是任務執行太慢導致的。

仔細查看了代碼發現只有一個地方可能會慢:也就是有一個數據庫的查詢。

把這個 sql 拿到生產環境執行發現確實不快,查看索引發現都有命中。

但我一看表中的數據發現已經快有 7000w 的數據了。同時經過運維得知 mysql 那臺服務器的 io 壓力也比較大。

所以這個原因也比較明顯了:

由于每消費一條數據都要去查詢一次數據庫,mysql 本身壓力就比較大,加上數據量也很高所以導致這個 io 響應較慢,導致整個任務處理的就比較慢了。

但還有一個原因也不能忽視;由于所有的業務線程在某個時間點都進入了死循環,根本沒有執行完任務的機會,而后面的數據還在源源不斷的進入,所以這個隊列只會越堆越多!

這其實是一個老應用了,可能會有人問為什么之前沒出現問題。

這是因為之前數據量都比較少,即使是并發寫入也沒有出現并發擴容形成環形鏈表的情況。這段時間業務量的暴增正好把這個隱藏的雷給揪出來了。所以還是得信墨菲他老人家的話。

總結

至此整個排查結束,而我們后續的調整措施大概如下:

  • hashset 不是線程安全的,換為 concurrenthashmap同時把 value 寫死一樣可以達到 set 的效果。
  • 根據我們后面的監控,初始化 concurrenthashmap 的大小盡量大一些,避免頻繁的擴容。
  • mysql 中很多數據都已經不用了,進行冷熱處理。盡量降低單表數據量。同時后期考慮分表。
  • 查數據那里調整為查緩存,提高查詢效率。
  • 線程池的名稱一定得取的有意義,不然是自己給自己增加難度。
  • 根據監控將線程池的隊列大小調整為一個具體值,并且要有拒絕策略。
  • 升級到 jdk1.8。
  • 再一個是報警郵件酌情考慮為電話通知。

hashmap 的死循環問題在網上層出不窮,沒想到還真被我遇到了。現在要滿足這個條件還是挺少見的,比如 1.8 以下的 jdk 這一條可能大多數人就碰不到,正好又證實了一次墨菲定律。

好了,以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對服務器之家的支持。

原文鏈接:https://crossoverjie.top/2018/11/08/java-senior/JVM-concurrent-HashSet-problem/

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 我与么公激情性完整视频 | ai换脸杨颖被啪在线观看 | 成人影院观看 | 国产成人福利免费观看 | 精品久久久久久影院免费 | 门卫老张和女警花小说 | 扒开女人下面使劲桶屁股动漫 | 欧美三级一区二区 | 青草碰人人澡人人澡 | 日本一卡二卡3卡四卡无卡网址 | 黄a在线观看 | 高清欧美videossexo免费 | a v在线男人的天堂观看免费 | 亚洲激情自拍偷拍 | 精品一区二区三区自拍图片区 | 500福利第一导航 | futa巨大好爽好长 | 4hc44四虎永久地址链接 | 青青草原免费在线视频 | 日韩在线资源 | 免费视频一级片 | 午夜在线观看视频 | 古代双性美人被老糟蹋 | 国产suv精品一区二区四区三区 | 青青草国产青春综合久久 | 欧美国产视频 | 好紧好爽再叫浪一点点潘金莲 | 99久久精品国产综合一区 | 日本中文字幕黑人借宿影片 | 91短视频版高清在线观看免费 | 免费在线观看网址大全 | www.午夜剧场| 丰满艳妇亲伦视频 | 双性np玩烂了np欲之国的太子 | 日韩中文字幕一区 | 蝴蝶传媒3o45 | 午夜影院网站 | 美女无内裤下部黄 | 久久最新地址获取 | 99久久综合| 火影小南被爆羞羞网站 |