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

案例:(一人一单)
旧的:

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

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

一人一单操作:
第一步:查询订单嘛(才能判断(将下面的查询用户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(暴露)

测试:



莫得问题了