使用锁(同步锁、分布式锁)遇到的两个问题

本文讨论了在使用Redisson分布式锁和Sycn同步锁处理预约挂号并发问题时遇到的两个问题:锁的范围过小可能导致失效,事务范围过大可能超出锁影响。作者提供了调整锁范围、细化事务和使用乐观锁的解决方案。
摘要由CSDN通过智能技术生成

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

今天学习使用Redisson分布式锁和Sycn同步锁解决并发场景下的预约挂号问题时,遇到了一些问题:
(1)锁的范围要明确,范围小了可能会导致锁失效(比如库存是否足够的语句);
(2)事务如果加在锁外面,也会导致锁失效。


提示:以下是本篇文章正文内容,下面案例可供参考

简单说一下

1.锁的范围过小

本处出现的错误是锁的范围过小,查询库存数量的代码放到了锁外面:

 // 体检预约是否与约满,约满则无法预约
            OrderSetting orderSetting = orderSettingDao.selectByDate(orderDate);

这样会导致多个线程查到的库存都有余量。尽管判断和插入数据的操作在锁的内部,多个线程执行时候,该操作部分是串行执行的,但由于获取的剩余库存数都是有余量的。多个线程还是都会进行修改库存和插入记录表的操作。表现为锁失效。
解决方案:
分析好会影响到锁对象的代码段有哪些,如果锁失效了又分析不出来需要加锁的范围,可以尝试逐步扩大锁的范围,测试锁是否生效。

2.事务范围在锁的范围外

由于使用@Transactional来实现事务,注解是加在方法上的,导致事务范围过大,可能会包括锁的生效范围。
而事务范围在锁的范围外,可能会导致锁失效。
比如考虑一种情况:
线程A和线程B都要判断数据库表中是否还有库存,如果有,则让库存量减一(即代表使用了该库存)。此时库存量为1,只能满足一个线程使用,且库存量必然不能为负数。
① 线程A开启事务(事务隔离),获得锁,查询库存为1,发现可以使用库存,让库存数减1变为0。
② 如果在线程A释放锁之前,线程B也开启了事务(事务隔离),尝试获取锁,失败(因为此时锁被线程A持有),线程B自旋:休眠一段时间继续尝试获取锁。
③ 此时线程A执行完操作库存的代码,释放掉锁,但还没有提交事务。
④ 线程B获取到锁,进行操作库存的操作,此时由于事务隔离,线程B查询到库存余量也是1(实际上线程A已经获取了该余量),尝试获取余量(表现为修改库存余量减一),后续释放锁,提交事务。
⑤ 当两个线程都提交事务后会发现,库存余量变成了-1,两个线程都使用了这个库存资源。
这样就出现了超卖问题,锁失效。
解决方案:
问题的根源就是事务加到了锁的范围以外。因此减小事务的粒度,改变事务范围即可。
① 方案一:
将需要加事务的部分抽离出去,单独成为一个方法,在使用锁的方法里调用这些抽离出的方法即可,从而保证加锁范围不在事务范围内。
② 方案二:
对于声明式事务@Transactional注解粒度太大的问题,可以使用编程式事务,比如使用TransactionTemplate、TransactionManager等,手动控制事务。
③ 方案三:
使用乐观锁,在数据库层面上使用乐观锁,可以保证在事务隔离的情况下的更新安全性(意思就是:虽然查询的情况下能改,但是使用sql语句更新时发现改不了),比如更新库存时候使用下面一段语句:

UPDATE t_ordersetting SET reservations = number - 1 WHERE id = #{id} AND number > 0

如果数据库中库存为0了,即时当前线程(因为事务隔离)以为库存数仍大于0,在进行更新的时候也会更新失败(幻读)。


总结

写得不甚流畅也不太完善。日后有时间再修改吧。总之锁失效了可以考虑一下这两种因素,看看能不能解决失效的问题。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值