写在前面:工作有几年了,从入门到现在,遇到也解决了一些问题。(当然,框架级别的暂时还没有)一直以来,都是从博客园以及其他各大社区搜罗出来的各种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 = new ArrayList<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。