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取錯了。
總結:多線程環境下,對於可變的變量,在抽象基類中,慎用靜態變量。
由於各實現類都引用了基類的靜態類變量(即使子類中本身定義了同名變量,也與基類中的變量不是一個存儲空間)。該基類靜態變量只有這麼一個存儲空間。 因此會很容易被修改,致使意想不到的結果。