项目场景:
为了增加代码的代码可拓展性,降低耦合性。封装了一个根据不同的参数,可以调用不同的处理器(action)的框架。原本一切正常的代码,却在性能测试中出现了异常。是什么导致原本正常的代码在并发下却出现了异常呢?本文将探讨单例更改属性对并发的影响。
问题描述
@Service
public class TestServiceImpl implements TestService {
public String test() {
//做一个集合模拟入参
List<String> testList = new ArrayList<String>();
Collections.addAll(testList,"A","B","C","D","E");
//遍历集合
for (String s : testList) {
//①获取process(处理器action封装在此对象中)
TestProcess process = ProcessBeanFactory.getProcess(s);
//②执行
String result = process.execute();
//③把结果存到ThreadLocal中
YukinoThreadLocal.add(s,result);
}
//removeTheadLocal
YukinoThreadLocal.clear();
//返回
return "success";
}
}
示例是一段简单的代码,模拟了根据页面传来不同的参数来调取不同的处理器,以达到通过页面来控制CRUD的功能,降低了代码的耦合性,增加可拓展性。
这样一段简单的代码却在并发运行下出现了错误,ThreadLocal中储存的键和值出现了不匹配的问题。
原因分析:
单例模式更改属性,导致在并发下出现了错误。
我们先来看一下ProcessBeanFactory是如何获得process的:
@Component
public class ProcessBeanFactory {
public static TestProcess getProcess(String s){
//获得process对象(处理器action封装在此对象中)
TestProcess process = YukinoContext.getBean(TestProcess.class);
//根据参数s获取用于具体执行的action
YukinoAction action = YukinoContext.getBean(s, YukinoAction.class);
//封装bean对象里用于执行的属性
process.setAction(action);
//返回
return process;
}
}
再来看一下TestProcess 内部是如何封装和调用处理器action的:
@Component
public class TestProcessImplA implements TestProcess {
private YukinoAction yukinoAction;
//封装处理器
public void setAction(YukinoAction yukinoAction) {
this.yukinoAction = yukinoAction;
}
//执行处理器
public String execute() {
return yukinoAction.execute();
}
}
这里解释一下为什么要用Process封装处理器action,而不直接调用action呢?这样做不是多此一举吗?其实这里还可以封装一些其他的对象,如处理器action的前置增强,后置增强等。方法也可以变成执行前置,执行后置,全部执行等多种,让处理器的使用更为灵活。
到此问题的原因显而易见了,TestProcess是通过工厂模式从springboot中获得的,采用的是默认的单例模式。因此在封装处理器(更改属性)时受到了其他线程的影响,导致在第二步执行的时候,用了错误的处理器,拿到了错误的结果,从而引起了ThreadLocal中储存的键和值出现了不匹配的问题。
解决方案:
@Component
public class ProcessBeanFactory {
public static TestProcess getProcess(String s){
//获得process对象(处理器action封装在次对象中)
TestProcess process = new TestProcessImplA();
//根据参数s获取用于具体执行的action
YukinoAction action = YukinoContext.getBean(s, YukinoAction.class);
//封装bean对象里用于执行的属性
process.setAction(action);
//返回
return process;
}
}
TestProcess不再使用单例模式即可。
因此在编写代码时应该小心单例模式的属性变化问题。