• 45、优惠券秒杀(实现一人一单)


    优惠券秒杀(实现一人一单)

     

    案例:(一人一单)

    旧的:

     

    这种优惠券优惠力度大应该一人只能消费一次。问题(我们一个人全抢了)

     

    优化:(我们在库存充足的时候不是直接扣减库存的,而是判断一下这个订单是否存在了。不存在才扣减库存)

     

    一人一单操作:

    第一步:查询订单嘛(才能判断(将下面的查询用户id提上来先用))

     

    第二步:判断是否存在了(不存在返回失败)

     

    第三步:不存在还是之前的扣减库存

     

     

    测试:(200并发应该只能卖一单)

     

     

    出现了错误(这个1个用户还是下了多单)

     

    问题分析:我们这里的逻辑与前面的库存思想是一样的,先查询再判断再扣减

    按时我们的这里是多线程操作(也是出现了多线程穿插的情况)即为当我们第一个线程在判断出这个用户不存在0,但是还未扣减库存(用户信息添加进数据库(没有订单))的时候,其他线程也进来获取了也是0,他的判断还是没有。所以逻辑还是会让它扣减的。

    解决(优化)思路:(还是加锁啊,这里就不能更新再来判断了,就必须是悲观锁了(syn))

    第一步:我们这个整个逻辑(一人一单)进行封装:Ctrl+m。(就是将下面的变为一个函数,上面使用的时候直接调动)

     

     

    封装好了(调用下面的封装函数)

     

    第二步:加锁我们是先加在方法上的(syn),但是这样就锁住了整个方法,每个用户来都要获取锁,串行效率很慢)而且我们的事务范围是这个扣减库存的操作而不是整个类上面(前面是查询不需要加事务嘛)所以我们就将事务放到下面的这个锁的方法上了。

    第三步:(优化:不要将整个方法锁住(提高效率),锁用户)

    思路:我们缩小锁的范围,(我们只锁我们的用户id从而锁住我们的用户)一人一单中,我们只有在同一个用户来的时候我们才进行判断了(他的并发安全问题),其他用户我们就不加锁了。我们就使用关键字这种方法了(syn)锁住这个id再将下面的加在代码块中。

     

    这里有个坑:

    坑1:(我们这里是将id值一样的作为一把锁)但是我们每一次请求来这个id对象其实都是不同的,我们的对象变了,那么我们的锁也就变了(我们锁的操作是对象的,这是基本别忘了)。我们这里的tostring是不能保证其是根据值来加锁的。请看源码,其底层调用的是这个Long的一个静态函数,内部是一个new String()。New了一个字符串。所以我们再每一次调用这个toString的时候也是一个全新的字符串对象。所以这个锁对象又变了一次。(即使id是一样的(字符串),但是字符串对象还是不一样的)

     

     

     

    优化1、(调用一个字符串方法,它的值相同返回的对象是一样的toString().intern())

    确保当我们的用户的id一样的时候,锁是一样的。(锁的是当前用户)

     

    坑2:(我们这里的锁范围又有点小了)我们的这个事务提交问题(我们是在方法内部加锁),我们这个事务是被spring管理的(@Transactiona)所以这个事务的提交是在我们函数执行完以后由spring才做的提交。

    逻辑:我们先去开启事务,开始执行,然后获取锁,我们开始做下面的查询,查询我们再减库存,提交订单。这时我们是先释放锁再才会提交事务。那么在这个函数的最后的}结束后,这个锁就已经释放了(锁释放到spring提交事务中间还有点时间)。那么其他的线程就能够进来了。此时事其他线程进来查询订单(此时事务还没有提交,我们下面新增的订单还没有写入我们的数据库(和前面的数据库事务串起来。我们修改的时候只是改的表的数据,只有提交后数据库才会永久更改了))那么他也会进行扣减库存操作了(还是出现并发安全问题)

    优化2、我们应该是在事务提交后才释放锁(我们将syn加在这个函数调用前面去加锁了)

     

    坑3、上面优化后又出现事务问题了(我们是对下面的那个函数加了事务,没有给外面的这个函数(方法)加事务而外面这个函数再调用的时候是this.这样调用的 。其中他this拿到的是(目标对象不是代理对象)这个对象,而不是我们需要的他的代理对象。而我们需要知道我们的事务想要生效是我们的spring对这个类做了动态代理,拿到了这个类的代理对象。然后用这个下面函数做了事务处理)this拿到的是目标对象,而不是代理对象(代理对象才能做很多目标对象无法做的东西操作)没有事务操作功能的(这就是spring事务失效的几种可能性之一)。

     

     

    优化3:我们去拿到我们事务的代理对象(AopContext.currentProxy()拿到代理对象也就是service接口嘛(别忘了我们代理这些都是实现了同一个接口))

     

     

     

    最后我们还需要加一个依赖org.aspectj

     

    再到这个启动类上去去加注解暴露这个代理对象true(暴露)

     

    测试:

     

     

     

    莫得问题了

  • 相关阅读:
    Spring基础知识整理
    【Python】解决Python报错:ModuleNotFoundError: No module named ‘xxx.yyy‘
    【iOS】知乎日报第三周总结
    Chapter3.3:时域分析法
    JDK8中HashMap底层源码解析-put和putVal方法以及数组下标的计算方式
    C#不安全代码
    iOS——【Blocks】
    Spring Cloud Alibaba(二)
    Audition 2024 for Mac/Win:音频录制与编辑的卓越之选
    学习笔记24--多传感器后融合技术
  • 原文地址:https://blog.csdn.net/logtcm4/article/details/127732901