事务碰上锁好似那油锅里进了火

目录

前言

 场景

代码复现        

提出疑问

该怎么解决呢

        1.使用编程式事务

          2.将事务独立出一个方法


前言

         很多时候我们谈起事务都是如虎色变,一想起来都是脑袋懵懵的

  1. 事务的隔离级别及传播机制是什么
  2. Spring的事务底层实现原理了解吗
  3. 哪几种情况下事务会失效       

        锁相关的更是让人如临大敌

  1. 可重入锁ReetrantLock和synchronized的区别
  2. 分布式锁的实现
  3. 轻量级锁volatile关键字的实现
  4. 说一说synchronized的锁升级流程

        当然了,大家都很厉害,上面这些稍微有点难度,仍可一力当之

        但是当事务遇上了锁,难上加难,阁下该如何应对呢。

        没开玩笑。

        在日常开发中,事务必不可少,锁也一样,那事务碰上锁,我们该怎么办呢

       


 场景

        我举一个经典的下单场景

                1. 扣减库存

                2. 生成订单

        首先我们会考虑加事务,防止以上哪步数据库操作失败,回滚数据

        再次考虑并发问题,加锁,防止超卖


代码复现        

        我这里有个库存表product,一共10件商品

        

        模拟多线程调用,抢购这些商品,并创建订单。

        我们预期商品库存会变为0,并且生成10笔订单数据。

        我们根据直觉很容易写出如下代码,在方法上使用事务注解,用于异常回滚,使用锁(此处为了方便使用ReentrantLock,多服务一般都是使用分布式锁)用于并发控制。

        抢单逻辑如下:

        下面这段代码,方法上加了@Transactional(rollbackFor = Exception.class)

        方法的开启和结束也使用Lock加上了锁。

         给你几秒,好好想下,输出会是什么?

        订单表会生成多少条数据?

        “创建订单成功了”这个日志输出会打印多少次?

       没错,生成了20笔订单!!!!!

       订单表中一共20笔交易数据。

        怎么会这样!!!

        

          日志输出也是整整20次

        如果是实际商品售卖场景,结果就是我们只有10件库存,由于我们这段代码有问题,多生成了一倍订单,生成了20笔订单。

        呐,货发不发出去是小事,工作可是要没了呀,这,这以后,以后还怎么带薪摸鱼呢    

        我们把@Transactional(rollbackFor = Exception.class)这行代码先拿掉再重新看下执行结果。当然实际情况下肯定需要加事务的,此处只是为了对比排错。

        可以看到只生成了10笔订单数据

提出疑问

        1.为什么会发生超卖,是锁使用的有问题还是事务使用的有问题

        2.为什么是正正好好多卖了一倍

       


      

        先看第一个问题,为什么会超卖呢?

        先问下,上述代码锁释放的代码,在try代码块执行完之后,finally代码块里执行,那么事务什么时候提交的呢,是在 this.orderMapper.insert(order);这条插入语句之后吗?

        很明显不是的。

        本文示例中事务是在方法执行结束之后提交的,熟悉Spring事务的同学们肯定知道,声明式事务@Transactional注解是基于代理的方式进行的。

        打上断点分析下

        可以看到在TransactionAspectSupport类中,invokeWithinTransaction()方法先对实际@Transactional注解修饰的方法代理执行后,最后才提交的事务

        也就是说在锁释放之后,事务还没有提交,中间是有程序在执行的那么一小段时间的,在这段时间内,如果新的线程进来,查询到库存还是原值,这个时候就会发生超卖。

        虽然这个时间点很小,但是并发量稍微起来点,谁也不能保证什么都不会发生。

        正如我们实例中展示,很容易就发生了超卖

        我这儿有篇收藏已久的Spring事务秘籍,v我50可点击查看

        死磕Spring之AOP篇 - Spring事务详解 - 月圆吖 - 博客园 (cnblogs.com)


        第二个问题,为什么一直是正好是创建了20条数据订单呢

        其实不会一直是正好20条,只是大概率会20条。

        看下图,我们来分下下执行流程。

        假设这个时候库存还是10个

        1.线程1释放锁的时候,扣减库存之后,事务还没来的及提交

        2.这个时候线程2拿到了锁,由于线程1的事务还没有提交,线程2读到的库存数据还是10个,这个时候很大几率就会产生超卖了,注意哦,这里并不是一定会产生。

        3.关键点是线程3这个时候是参与不进来的,它很想进来,但法律不允许3p,不对。。是没有锁的权限,它只能等着线程2去释放锁。

        所以,超卖的情况下最多只能有两个线程,大概率是库存的两倍 20个。

        当然,我们可以尝试复现下创建订单数不是20个的场景。

        我们在每个线程加锁之前先让线程睡一会儿,目的是让之前的线程事务提交完毕再去获取锁,再去查询库存,这样就有几率避免超卖,要根据你系统运行环境的性能来调试。

        

        可以看到结果创建订单数就不会是20个了,而是13个

该怎么解决呢

        1.使用编程式事务

          为了方便,我这里使用  TransactionTemplate。

  

     

        2.将事务独立出一个方法

        注意哦,这里是会有坑的,不要定义成private方法,不要同类中直接调用。

        抽出doSell()方法,将事务操作独立出来,直接调用是不会生效为了演示方便,我这里当前类自己注入自己

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这是一个Bootstrap框架下的网页布局代码,其中包括三个部分,每个部分都包括一个标题和一段文字说明,如下所示: 第一个部分: <div class="col-lg-4 col-md-4"> <div class="event-info"> <h4 class="text-center slideanim">泡温泉</h4> <p class="eve slideanim">沙城是中国最大的温泉城市,有着丰富的温泉资源,每年吸引着大量的游客前来泡温泉。</p> </div> </div> 第二个部分: <div class="col-lg-4 col-md-4"> <div class="event-info"> <h4 class="text-center slideanim">菜被子</h4> <p class="eve slideanim">在沙城除了泡温泉,买葡萄最应该打卡沙城的特色小吃“菜被子”。不同于肉夹馍,月亮馍所有素的,荤的都要经过油锅的洗礼,刷上自制麻酱。咬一口食材酥脆,刷满麻酱的面饼与食材味道完美结合。上至孩子爸妈,下至刚上学的孩子,每个人到这来只有一个目的,那就是给自己味蕾一个交代。</p> </div> </div> 第三个部分: <div class="col-lg-4 col-md-4"> <div class="event-info"> <h4 class="text-center slideanim">葡萄采摘</h4> <p class="eve slideanim">沙城是葡萄之乡,葡萄种植历史悠久,品种丰富,每年9月份是葡萄采摘的季节,游客可以来到葡萄园,品尝新鲜的葡萄,体验采摘的乐趣。</p> </div> </div> 其中,col-lg-4 col-md-4表示该部分占用网页布局的4分之1,即一行3个部分。h4标签表示标题,p标签表示文字说明。slideanim是一个自定义的CSS动画效果类,可以实现文字和图片的动态滑入效果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值