您的位置 首页 java

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

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

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

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

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

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

  • 使用 synchronized 或lock锁

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

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

  • 使用数据库行级锁

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

实现这个,在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);
 

这样通过事务和行级锁,即使并发,程序应用依然会在数据库有行锁的情形下,只对那行数据操作一次,直到它完成当次的任务。

文章来源:智云一二三科技

文章标题:「java」一次并发防重复操作的处理经验

文章地址:https://www.zhihuclub.com/198610.shtml

关于作者: 智云科技

热门文章

网站地图