1 问题描述
最近再做火车票购票时,在对票类库存进行扣减,有线程安全的问题,遂加锁lock进行同步,
但发现加锁后并没有控制住库存线程安全的问题,导致库存仍被超发。
先简单介绍下,各层的技术架构:
中间层框架:SpringBoot
持久层:MyBatis
MVC框架:Spring MVC
存在问题的代码:
@Transactional
@Override
public List updateTables() {
//查询库存,根据库存判断是否具有购买资格,具有资格则更新库存
//其中mysql操作包含select和update
//更新trade,payment,插入tickets
}
理论情况具体描述:
当库存剩余为1时,线程1拿到锁进入同步代码块,扣减库存,线程2等待锁;
当线程1执行完同步代码块时,线程2拿到锁,执行同步代码块,检查到的库存剩余仍为0
2 排查问题
查找资料发现spring的事务引起的,因为Spring的@Transactional是AOP实现的,它是在函数开始前开启事务,在函数执行完毕后提交事务。所以上面代码中锁的释放是在事务提交之前,释放锁但是还未提交事务,所以另一个事务紧跟着执行,该事务查询的库存是还未提交的老数据。
3 解决方法
因为能力有限,所以目前的解决方法是将判断库存和更新的代码移动到controller中,不将他放在事务中,并且该代码中不使用事务
4总结
根据以上的排查过程,已经很清楚的确认了事务与锁之间存在的问题。由于事务范围大于锁代码块范围,在锁代码块执行完成后,此时事务还未提交,导致此时进入锁代码块的其他线程,读到的仍是原有的库存数据。
关于程序加锁自己的一点见解:
(1)建议程序中尽量不要加锁;
(2)如果使用锁,尽可能使用分布式锁
(3)使用锁的过程中,尽量减小锁粒度,尽量减小锁的代码范围;