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)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值