生產環境遇到的hashMap非線程安全問題java.lang.thread.waiting
寫在前面:工作有幾年了,從入門到現在,遇到也解決了一些問題。(當然,框架級別的暫時還沒有)一直以來,都是從博客園以及其他各大社區搜羅出來的各種fix方法。目前稍有閑暇時間,在看過大V沈劍的博文後,我也鼓起勇氣來書寫博客,記錄工作中遇到和解決的問題(其中當然也包括我在博園獲取的各種解決方法;能找到原博文的小弟一定會註明出處。)因為總覺得自己水平不夠,怕寫出來的文章誤導了別人。以下是這周生產環境遇到的一個問題,寫出來供大家參考。
現象
周五一大早,車子都沒停穩(電動車),群裏就開始在詢問誰最近的代碼有比較耗時耗性能的操作,大家都說沒有。然後就是一張截圖:
cpu已經飆到1600+,這個問題還是比較嚴重的。隨後聯系到運維組給出tomcat運行日誌排查是否有錯誤。經過排查日誌發下如下錯誤:
看到後飛速坐到位置上,去查詢對應的錯誤代碼;
分析和搜索:
以下是部分代碼截圖:
1 public static Map<String,String> cityCodeMap = new HashMap<String, String>(); 2 3 public static Map<String,String> provinceMap = new HashMap<String, String>(); 4 5 public static List<String> sensitiveWordList = newArrayList<String>(); 6 7 public static String getCityCodeByNumber(String phoneNumber) 8 { 9 String start = StringUtils.substring(phoneNumber, 0, 7); 10 if(StringUtils.isEmpty(start)) 11 { 12 return "000"; 13 } 14 Map<String, String> cityCodes = getCityCodes();15 String cityCode = cityCodes.get(start); 16 if(StringUtils.isEmpty(cityCode)) 17 { 18 return "000"; 19 } 20 return cityCode; 21 }
主要用於獲取手機號碼的號段對應的城市編碼,項目的某個模塊(不能打廣告吧)需要用到。並且每次調用都用用到;咋一看,感覺沒啥問題,項目中好多地方都是這麽幹的。接著又在測試環境和本地環境跑一邊代碼,都運行正常。
然後就搜索了java.lang.thread.waiting 這個異常,在這篇博文中搜到相關問題,http://www.cnblogs.com/zhengyun_ustc/archive/2013/03/18/tda.html
才發現可能是多線程引發的問題。
這裏列舉兩個比較解釋特別詳細的博文,源碼跟蹤和分析過程都非常詳細。
http://coding-geek.com/how-does-a-hashmap-work-in-java/
https://coolshell.cn/articles/9606.html
此種情況雖然是概率發生的,但是在並發量比較大的情況下,還是及其危險的。如果發現不及時,很有可能導致單點故障甚至整個集群不可用。又根據自己項目代碼的情況分析,主要是因為在初始化中,循環向map中put新元素導致map擴容rehash時產生了死循環。
初始化代碼:
public static Map<String,String> getCityCodes() { if(cityCodeMap.isEmpty()) { Set<String> keySet = ResourceBundle.getBundle("config/citynum").keySet(); for (String key : keySet) { String cityCode = PropertiesUtil.getKey("config/citynum",key); cityCodeMap.put(key, cityCode); } } return cityCodeMap; }
ps:此處的citynum中有幾萬個鍵值對。
解決辦法:
- Hashtable
- ConcurrentHashMap
- Synchronized Map
可自行搜索實現原理,很多大神、大仙兒都闡述的比我詳細。
都說程序員都是懶人,我不認同。我們只不過是想用最少的代碼去解決問題。所以,我們的改良方案就是把HashMap直接換成ConcurrentHashMap。
生產環境遇到的hashMap非線程安全問題java.lang.thread.waiting