Java学习(十八)-锁的使用

一、锁类别
1、悲观锁(synchronize)
  • Java 中的重量级锁 synchronize
  • 数据库行锁
2、乐观锁
  • Java 中的轻量级锁 volatile 和 CAS
  • 数据库版本号–常用
3、分布式锁(Redis锁)–常用
二、实际案例

库存扣减的案例,商品库存100,操作1扣减100,操作2扣减100,如何避免操作1和操作2并发时都成功造成的超卖

方案1:同步排它锁(synchronize)。但是排他锁的缺点很明显:
  • 线程串行导致的性能问题,性能消耗比较大。这种读写都是需要串行么??
  • 无法解决分布式部署情况下跨进程问题;???
方案2:数据库行锁

用数据库行锁来锁住这条数据,这种方案相比排它锁解决了跨进程的问题,但是依然有缺点:

  • 性能问题,在数据库层面会一直阻塞,直到事务提交,这里也是串行执行。指读写都阻塞么??
  • 第二个需要注意设置事务的隔离级别是Read Committed,否则并发情况下,另外的事务无法看到提交的数据,依然会导致超卖问题;
  • 缺点三是容易打满数据库连接,如果事务中有第三方接口交互(存在超时的可能性),会导致这个事务的连接一直阻塞,打满数据库连接。
  • 最后一个缺点,容易产生交叉死锁,如果多个业务的加锁控制不好,就会发生AB两条记录的交叉死锁。
方案3:redis分布式锁

redis加锁的命令setnx,设置锁的过期时间是expire,解锁的命令是del。分布式锁的优缺点:

  • 优点:
    1、可以避免大量对数据库排他锁的征用,提高系统的响应能力;
  • 缺点:
    1、设置锁和设置超时时间的原子性;
    2、不设置超时时间的缺点;
    3、服务宕机或线程阻塞超时的情况;
    4、超时时间设置不合理的情况;
1、加锁和过期设置的原子性–可忽略缺点(已优化)
  • 场景一:触发死锁。2.6.12之前的版本中,加锁和设置锁过期命令是两个操作,不具备原子性。如果setnx设置完key-value之后,还没有来得及使用expire来设置过期时间,当前线程挂掉了或者线程阻塞,会导致当前线程设置的key一直有效,后续的线程无法正常使用setnx获取锁,导致死锁。
  • 场景二、极端锁失效场景。redis2.6.12以上的版本增加了可选的参数,可以在加锁的同时设置key的过期时间,保证了加锁和过期操作原子性的。极端问题比如分布式环境下,A获取到了锁之后,因为线程A的业务代码耗时过长,导致锁的超时时间,锁自动失效。后续线程B就意外的持有了锁,之后线程A再次恢复执行,直接用del命令释放锁,这样就错误的将线程B同样Key的锁误删除了。代码耗时过长还是比较常见的场景,假如你的代码中有外部通讯接口调用,就容易产生这样的场景。
2、设置合理的时长,以防锁永远无法失效
  • 刚才讲到的线程超时阻塞的情况,那么如果不设置时长呢,当然也不行,如果线程持有锁的过程中突然服务宕机了,这样锁就永远无法失效了。同样的也存在锁超时时间设置是否合理的问题,如果设置所持有时间过长会影响性能,如果设置时间过短,有可能业务阻塞没有处理完成,是否可以合理的设置锁的时间
3、上面问题的解决方案–续命锁
  • 我们可以先给锁设置一个超时时间,然后启动一个守护线程,让守护线程在一段时间之后重新去设置这个锁的超时时间,续命锁的实现过程就是写一个守护线程,然后去判断对象锁的情况,快失效的时候,再次进行重新加锁,但是一定要判断锁的对象是同一个,不能乱续。
方案4:数据库乐观锁

数据库乐观锁加锁的一个原则就是尽量想办法减少锁的范围。锁的范围越大,性能越差,数据库的锁就是把锁的范围减小到了最小。
对表数据加个版本号的字段,对数据进行操作的时候,用版本号进行判断

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值