中年|架构师写的BUG,非比寻常( 二 )


答案显然是可以的 , 但需要注意重写hashCode和equals方法 。 如果忘记重写的话 , 大概率会造成内存泄漏 。
很不幸 , 现实中忘记的案例很多 。 大牛架构师也会中招 。
代码重写hashCode和equals方法后 , 线上就再也没发生过内存溢出 。

等等 , 还没完 。 毕竟是架构师 , 仅仅这样一个bug还是证明不了水平的 。 架构师写的bug , 肯定非比寻常 。
这种事出现的多了 , 研发领导对技术的权威性就不再是那么感冒 。 我们决定从并发量最高的代码开始 , 进行一下代码review 。
很不幸 , 架构师的visit代码出现问题了 。 虽然问题不是很大 , 但它毕竟是个问题 。
中年|架构师写的BUG,非比寻常
本文插图

在统计数据的时候 , 代码使用了ConcurrentHashMap , 但它并没有什么卵用 。
visit方法 , 首先拿出了key , 然后判空 , 再塞值 。 这明显不是一个原子操作 。
线程1:获取key为a的值线程2:获取key为a的值线程1:a为null , 生成一个b线程2:a为null , 生成一个c线程1:保存a=b线程2:保存a=c
此时 , B丢了 。
业务可以忍受 , 但严谨的技术大牛们忍受不了 , 提出了修改的意见 。
架构师说 , 给visit方法加个synchronized不就成了 。
public synchronized void visit(String url, String desc, long timeCost)
我说不行 。 有更优雅的写法 , 效率更高 。 那就是使用putIfAbsent方法 , 代码改动如下:

MonitorKey key = new MonitorKey(url, desc)MonitorValue value = http://news.hoteastday.com/a/monitors.putIfAbsent(key, new MonitorValue())value.count.getAndIncrement()value.totalTime.getAndAdd(timeCost)value.avgTime = value.totalTime.get() / value.count.get()
大家就这两种方式争论了起来 。
技术总监托着腮想了半天 , 看了看争的面红耳赤的同学们 , 说:这就是我不放心你们的缘故 。 线上环境要尽量保持稳定性 , 做最小的变更 。 既然加个synchronized就能够很容易简单解决的问题 , 为啥不直接用呢?下面这种代码改动太大 , 有风险 。
总监接着把头转向我:这个BUG非比寻常 , 为了让大家引以为戒 , 你来做整个事故的复盘 。 把问题的排查和得到的教训分享给大家 , 让大家向这种至简的架构看齐 。 我们平常的工作中 , 也要尽量以结果导向为主 , 用什么手段无所谓 , 能漂亮把事情办好就行 。
这就是此篇文章的由来 , 我虚心受教 , 同时也明白自己的工资是涨不上去了 。


推荐阅读