synchronized塊使用string作爲鎖遇到的坑

一個訂單要給多個供應商派單,爲了防止重複提交,在給業務代碼添加了一個 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回收。

參考文檔來自:java string中的比較難注意細節(intern,subString和gc回收String)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章