N年没有写过博客了……多线程
开始:app
2018.08.03 搬家项目,版本昨晚刚上线,今早测试与供应商的估价接口,发现问题。ide
背景:post
我司对接三家供应商A、B、C,各家的Url,appid不一样,分别配置在配置文件中。测试
抽象类(截取部分片断):lua
1 public abstract class AbstractSupplierOrderService implements ISupplierOrderService { 2 3 4 protected static ISupplierConfig supplierConfig; 5 6 @Autowired 7 private MovTaskMapper movTaskMapper; 8 9 //根据供应商code 获取对应供应商服务实例 10 public static ISupplierConfig getSupplierConfig(String supplierCode) { 11 if (SupplierEnum.LANXINIU.getCode().equals(supplierCode)) { 12 return SpringContextUtil.getBean(SupplierConfigLanxiniu.class); 13 } 14 if (SupplierEnum.ZIROOM.getCode().equals(supplierCode)) { 15 return SpringContextUtil.getBean(SupplierConfigZiroom.class); 16 } 17 if (SupplierEnum.SITONG.getCode().equals(supplierCode)) { 18 return SpringContextUtil.getBean(SupplierConfigSitong.class); 19 } 20 return null; 21 } 22 23 @Override 24 public ResultInfoVo quotedPrice(EvaluateOrderPriceVo orderVo) { 25 supplierConfig = AbstractSupplierOrderService.getSupplierConfig(orderVo.getSupplierCode()); 26 String quotedPriceUrl = supplierConfig.getQuotedPriceUrl(); 27 28 ResultInfoVo resultInfoVo = null; 29 EvaluateOrderPriceVo evaluateOrderPriceVo = null; 30 31 try { 32 Map voMap = ObjectToMapUtils.objectToMapString(null, orderVo, ""); 33 Map paramMap = getSupplierService(orderVo.getSupplierCode()).getOrderParams(voMap); 34 35 log.info("=============调用供应商获取报价信息接口入参:{}=============", JSONObject.toJSONString(paramMap)); 36 resultInfoVo = postSupplierServices(orderVo.getSupplierCode(), evaluateRestTemplate, quotedPriceUrl, paramMap); 37 } catch (Exception e) { 38 log.error("=============调用供应商获取报价信息异常,入参:{}=============", JSONObject.toJSONString(orderVo), e); 39 throw new OrderServiceException("调用供应商获取报价信息异常", e); 40 } 41 42 log.info("=============调用供应商获取报价信息接口出参:{}=============", JSONObject.toJSONString(resultInfoVo)); 43 44 return resultInfoVo; 45 } 46 47 }
调用入口:url
1 public ResultInfoVo evaluateOrderPrice(EvaluateOrderPriceVo evaluateOrderPriceVo) { 2 ===========省略========== 3 log.info("========估价流程开始,供应商code:{}========", evaluateOrderPriceVo.getSupplierCode()); 4 //动态获取服务商实例 5 ISupplierOrderService supplierOrderService = AbstractSupplierOrderService.getSupplierService(evaluateOrderPriceVo.getSupplierCode()); 6 //调用供应商获取报价接口 7 try { 8 quotedPriceResult = supplierOrderService.quotedPrice(evaluateOrderPriceVo); 9 } catch (Exception e) { 10 } 11 ===========省略========== 12 }
抽象类中有静态变量:supplierConfig。而 静态变量位于抽象类类对象的方法区,三个实现子类共用该静态变量。 如各子类对该静态变量赋值需求不一样,在多线程状况下,会出现问题。spa
排查问题时的日志:线程
日志中能够看到,问题出在线程2,8。 3d
分析:
线程二、一、8前后进入估价流程MovOrderService.evaluateOrderPrice,
一、线程8先根据对应的1003的supplierConfig实现,组装参数,生成sign。 实现类中取的 supplierConfig是在实现类中的一个类变量,因此不会共用抽象类中的类变量,取得apppid是对的
二、线程2根据抽象类中的静态类变量supplierConfig获取url。但此时supplierConfig被线程2更新成了线程2对应的实例,取的是线程2的对应配置。因此url取错了。
总结:多线程环境下,对于可变的变量,在抽象基类中,慎用静态变量。
由于各实现类都引用了基类的静态类变量(即使子类中本身定义了同名变量,也与基类中的变量不是一个存储空间)。该基类静态变量只有这么一个存储空间。 因此会很容易被修改,致使意想不到的结果。