java 防并发_「java」一次并发防重复操作的处理经验

场景是这样的,用户发起一笔交易,从一个数据域扣一个数值,生成一个订单。

在最初的代码里,没有对这块进行一些处理,从业务逻辑上来看,是可以走通的。

98e93f54bccd8f669f54a01ecec89180.png

业务逻辑完整,于是我在测试类中,创建了一个多线程的测试方法,来同步访问逻辑层的处理接口。这时候就可以看到数据出现异常了:一笔扣除,产生了多条订单。

原因其实很自然,多个线程同时访问逻辑层时,“查校账户数据”那步,本质上是拿出此刻的数据,然后做一个判断。在你还未修改这个数据之前(持久化),所有同步过来的判断都为“真”,没有任何控制的情况下,就会插入多笔同样的订单。当然,在每次请求都有一定间隔的情况下,很难出现。

dbd11d4b688f25ca61db087535e40196.png

这时候可以有多种处理方式,并不一定每种都是最好或不好,只就能实现来说说。

使用synchronized或lock锁synchronized可以直接在接口方法上加,改起来自然是最快的,如果你的事务在逻辑处理上没有问题,这样做在单节点的情况下,确实可以实现。但是自然不推荐。

synchronized锁的是对象,这样会让你实现类里其他的方法也阻塞了,程序响应会很长,肯定是非常不可取的。再加之即使是锁了 方法,那不同用户操作不同账户,应可并发进行更合理,所以这样一来,这个情景下,这两个都可以排除。

808672bc87da6d68746d1713c55a27ca.png

使用数据库行级锁分析了情景后,发现主要问题其实是在一条数据在使用过程中,在他的某值在某个合理数值下时,做一些其他操作,之后来修改这条数据的这个值。因此,在使用这条数据时,对它进行锁定就行了。当然,这必须要在一个事务内完成。

实现这个,在springboot中也比较简单,首先,在实现方法上打开事务:

@Transactional然后在做更新这条数据和拿到这条数据做检验之间,进行锁定。

以mysql,Oracle为例,简单点的策略,在select语句后后,加上for update,就可以实现锁,但是这里一定注意,确保你的where字段让你的SQL定位到一行为好,特别是这种情形,比如唯一的用户主键、订单主键来做定位条件会比较好。

而这个时候的释放锁,也很符合业务逻辑,即update这个数据对象后,就会自动释放锁。

示例:

// 获取行级锁 Order orderLock = orderMapper.getOrderLockById(order.getId()); if(orderLock.getStatus().equals(OrderStatus.SUCCESS)) { orderLock.setStatus(OrderStatus.TRANOUT); // 其他逻辑 }// 释放锁 orderMapper.updateByPrimaryKeySelective(orderLock);这样通过事务和行级锁,即使并发,程序应用依然会在数据库有行锁的情形下,只对那行数据操作一次,直到它完成当次的任务。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值