本文正在参加「Java主题月 - Java Debug笔记活动」,详情查看<活动链接>
前言
- 项目开发阶段已经告终了,顺利进入测试阶段而且反向还算正常。基本的流程功能都是可以走通的
- 下面测试开始进行性能测试,刚开始对接口定的目标是5000并发,因为我们的项目是传统型项目所以不需要支持那么大的并发。但是当上到300的时候我的接口就开始报错了。
- 还是那句话功能正常只是实验性的产品。能够抗住并发才算是真正的上线。量变引起质变
业务现象
- 项目中有个地方调用A接口,自己在页面上操作是没有任何问题。可以正常获取数据
- 通过jmeter等压测工具进行压力测试时改接口就会出现异常。也是鬼一般的问题。时而正常时而不正常
问题分析
- 经过逐层追踪和idea的断点调试终于定位到了问题方法了。这段代码初次看没啥问题
- 一开始看到这段代码我以为是配置文件位置不对。但是转念一想如果位置不对的话那就压根不可能成功的。所以问题还是出在代码里。
- 简单梳理下发现我们在获取配置文件中参数集合时每次都会先初始化resultMap 。 这个resultMap就是用来存放结果集的,
- 既然已经获取到
application.yml
.而且我们的项目也没有接入springcloud config
模块那么为什么每次都需要重新载入呢?关键是每次载入这个过程并没有加锁 - 没有加锁就导致了A线程执行读取后并完成了赋值操作在返回之前,B线程又开始执行该方法将resultMap清空了。这样就导致A线程明明已经装在了配置文件但是获取参数时确实空导致报错。
- 这种典型的问题就是开发者在开发阶段没有掌握并发问题。或者说没有考虑并发的问题。而我就是那个受害者因为这个问题又耽误了我宝贵的时间
解决方案
- 既然是并发导致的问题。那么加锁是最简单的解决方案。
- 但是具体问题也得具体对待啊。而且加锁也并不一定能解决问题。加锁就会带来性能问题。
方案一
- 首先在不加锁的情况下,我们完全可以将
resultMap = new HashMap<>();
这段代码去掉就可以解决问题。因为不在会被清空 - 但是问题是每次获取都会执行已在载入过程。这样太浪费了
方案二
- 然后就是加锁。并却出清空的操作并且将
resultMap
用volatile
修饰。这样避免清空操作且上锁保证顺序执行并且保证resultMap
线程可见 - 除此之外、我们还需要加上一层判断
if (resultMap.size() > 0) {
return resultMap;
}
总结
- 质变引起量变这是我们开发者必须考虑的事情。并发才是高阶程序员该做的。
多谢点赞