Transcational与Synchronized或Lock搭配使用导致并发问题
在多线程的情况下,我们经常会用到synchronized或者Lock来保证我们的线程安全。
但是当碰到Transcational之后又会碰撞出什么火花呢?
相信我,看完之后,你一定不会亏
首先回顾一下小知识点:
基于@Transactional注解的是 声明式事务
spring还提供了另外一种创建事务的方式,即通过手动编写代码实现的事务,我们把这种事务叫做:编程式事务。
数据库连接本来默认是自动提交的,加上注解Transactional之后,会设置为手动提交。
自动提交意味着 每当我们执行一个update ,delete或者insert的时候都会自动提交到数据库,无法回滚事务。
下面我们来看一个例子:
这个方法的执行流程是这样的。
很显然,涉及库存和订单,都会对数据库进行操作,且应该是应该原子性的操作。
所以我们在方法上加了一个 @Transactional 注解。
接着我们用Jmeter进行并发测试 15个线程 抢购数据库现有的10个库存。
执行完之后,我们发现本来10台库存,现在15个人居然都抢到了,还剩了2台。
真是离谱Mother给离谱开门,离谱到家了。此刻我真的想说,I love you Mother。
本来我们想象的执行流程应该是这样的:
造成现在超卖这种情况,只能说明我们的执行流程是这样的。
原因就在于,加上Transactional 之后,代码执行顺序变成了,先执行开启了事务,然后在上锁。最后的时候先解锁,然后提交事务。
在解锁和提交事务这个间隙中,使得其他线程有了趁虚而入的机会。上一个线程还没提交事务呢,下一个线程已经进来了,并且读取到的库存数量还是上一个线程修改之前的库存量。
所以,正确的使用锁:把整个事务放在锁的工作范围之内。即在进入事务方法之前就加上锁。
因此,我们可以这样解决:
想这样解决的朋友,回去等通知吧。赶紧去搜一下事务失效的几种场景
正常使用的话,可以这样:
第一种:
新建一个service,将之前lock锁之后的代码逻辑,放到另外一个service方法当中,然后再进行调用。
第二种:自己注入自己的方式
同样的,如果使用synchronized的话,也会出现同样的问题。
解决方案参考上面的方式,也可以这样。
参考文章 歪歪大神 https://juejin.cn/post/6999476143699001352#heading-3