一個訂單要給多個供應商派單,爲了防止重複提交,在給業務代碼添加了一個 synchronized塊進行串行化執行,重點是把訂單id作爲該同步塊的鎖,只有同一個訂單的執行業務時纔會串行執行,而不同的訂單會並行執行。
代碼是這樣的:
public boolean addNewPurchaseDistribute(List<String> supplierIds, String purchaseId) {
//同一個採購單派單需要串行化執行,防止重複派單
synchronized (purchaseId){
//判斷採購單是否處於派單中
PurchaseProcess purchaseProcess = purchaseProcessService.getProcessInfoById(purchaseId);
if (purchaseProcess == null) {
return false;
}
if (!StringUtils.equals(purchaseProcess.getSuppDeliveStatus(), SupplierOrderStatusEnum.WAITING_CONFIRM.getValue())) {
return false;
}
//過濾掉重複派單的供應商
supplierIds = supplierIds.stream().filter(supplierId -> purchaseProcess.getDistributeSupps().indexOf(supplierId) <0).collect(Collectors.toList());
if(CollectionUtils.isEmpty(supplierIds)){
return false;
}
//錄入派單列表
PurchaseDistribute distribute = new PurchaseDistribute();
distribute.setPdAcceptStatus(PurchaseDistributeStatus.WAITING.getValue());
distribute.setPdProcId(purchaseId);
distribute.setPdDeleteFlag(YesNo.NO.name());
distribute.setPdDealPerson(SessionUtils.getUserId());
purchaseDistributeBusiness.addBatchToSuppliers(distribute, supplierIds);
//採購單的分派供應商更新
updateDistributeSupps(purchaseProcess.getDistributeSupps(), supplierIds, purchaseProcess);
//向供應商發送短信
purchaseSendInfoService.sendPurchaseMsgToSupplier(supplierIds);
//新增派單日誌
String names = getSupplierNames(supplierIds);
String content = "採購單 " + purchaseProcess.getInnerName() + " 已派單給" + names;
distributeLogService.addOne(content, purchaseId);
return true;
}
這樣表面上看起來是可以的,但是執行起來卻不盡人意!,因爲當你頁面重複點擊提交請求時,在controller層會有打印出兩個線程id,可在方法內打斷點的時候只會執行一次,看來並沒有串行化。
那到底這樣做是有什麼問題嗎?
我們分析到,這裏面的string雖然值是同一個,但是在controller層傳遞過來的參數其實是從兩個線程內存中取到的,地址不是同一個。所以,現在的問題就是如何保證鎖住的string是同一個。
那我在String類中的方法中找到一個intern()方法,它的方法解釋是“從String類中維護的常量池中取該值對應的字符串對象,如果沒有,會創建一個新的字符串對象”,就是把鎖換成這個
synchronized (purchaseId.intern()){
//相關業務代碼
}
這樣確實可以保證是同一把鎖,還有一點是需要關心的,就是在String類中的這個屬性是維護常量池的
/** The value is used for character storage. */
private final char value[];
它在jdk1.6及之前是存放在永久代內存區的,在jdk1.7之後是存放在堆內存中,是爲了既能保證字符串重複利用,也能在非引用狀態有可能被gc回收。