关于jvm锁失效原因以及解决方案

一、业务对象或锁对象是多例的情况下

原因:业务中一般使用的lock对象锁,lock锁的范围是针对同一个对象里面不同的线程,也就是说,jvm锁是对象锁,对象之间锁不共用

有兴趣了解更深的也可以看一下lock锁的大致执行流程:

解决方案:保证业务对象和锁对象是单例,例如利用单例设计模式,spring的ioc容器对象管理......

可能有的小伙伴就会想到,既然上面两种导致锁失效的原因都是因为对象之间没有共用一把锁,那是不是用类锁就能解决这个问题?没错,如果使用sychronized中的类锁确实能解决这个问题,可又产生了另一个问题,也就是公平性的问题,sychronized是非公平锁,线程之间抢占资源顺序是随机,没有先到先得的规则,是允许插队的。一般业务上是不允许的,举个例子,在秒杀的业务场景下,一般比的是谁的手速和网速快,但在非公平锁的环境下,有可能后面点的慢或网速慢的人抢到了该商品,那是不是对于那些狂练手速和疯狂蹭网的很不公平?(补一句:lock锁没有类锁)

能保证对象的单例确实能解决这一个问题,但是下面这两种情况产生的问题才是问题的核心。

二、在使用了spring事务注解的情况下(不单是jvm锁,大部分锁实现都会出现这个问题)

原因:spring事务是基于aop的方式实现的,是包裹着整个方法的(包括锁),事务不在锁的范围内,很容易出现并发执行的时候,a方法的事务还没提交上去,b事务就读了数据库的旧值。

举个例子:

代理类中的方法实际代码是这样的 :

 a事务还没提交,b事务就读取了数据库的旧值(流程图):

解决方案:

第一种: 用显示事务,将事务放在锁范围里面

第二种: 事务隔离级别改为读未提交(不推荐)

第三种: 再套一层方法,在外层方法使用锁

第四种: 最优雅的解决方法,将锁封装成注解的形式,并把优先级设置成比事务注解低

三、在服务集群的情况下

 原因:服务都不一样了,锁和对象自然也不一样(就和第一个情况下的环境一样)

解决方法:利用mysql的排他锁机制,将所有业务sql集中成一条sql(以上三种问题都能解决,但是不灵活,只能在业务允许的情况下使用)

代码:

public void decrStock(String productCode) {
    UpdateWrapper<Stock> wrapper = new UpdateWrapper<>();
    wrapper.eq("code", "a").
            gt("quantity", 0).
            setSql("quantity = quantity - 1");
    int updateStatus = stockMapper.update(new Stock(), wrapper);
    if (updateStatus == 0) {
        throw new StockException("当前库存不足");
    }
}

总结

     综上所述,我们可以发现jvm锁只适合在单体项目中并且业务需求简单的情况下使用,所以有条件还是使用分布式锁吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值