实战篇-07.优惠券秒杀-实现一人一单功能_哔哩哔哩_bilibili
1.之前的问题
虽然解决了超卖问题,但是无法保证一人只能买一张,容易发生黄牛行为。
2.解决方案
2.1订单查询:判断该用户是否已下单
在库存判断之前,判断用户id和优惠券id是否已经存在,如果存在就停止
但是因为先判断再执行,仍然会出现多线程穿插执行的情况。
为了解决多线程冲突,要加锁
乐观锁因为要根据修改判断版本号,所以只能适用于更新情况。对于多线程查询情况只能用悲观锁。
2.2对订单查询加悲观锁sychronized锁
但是没必要对整个方法加锁,所以这里把后面5,6,7封装到createVoucherOrder方法里。
2.2.1
如果对整个方法加sychronized锁,那么锁监视器是this(当前对象),那么不管什么用户来,监视器都是this,相当于串行执行了,性能差。
2.2.2
一人一单是同一个用户来造成的并发安全问题,所以需要用某个用户的特征作为锁监视器,锁住某个用户的并发操作,而不影响不同用户之间的操作。
toString返回值会new一个String,所以每次都是新对象,即便是同一个用户,每次的锁监视器都不一样。
2.2.3
所以需要intern()方法,相当于只要内容一样,就返回常量池中那个String的地址,而不会返回toString new的地址。
这样就能保证只要用户id一样,字符串内容一样,就能从常量池获取一样的引用返回值,从而保证同一用户锁监视器的一致性。
2.2.4释放锁的时机
因为上述代码锁只到return,而spring会在return之后函数结束之后才提交数据库事务,因此在锁释放到事务提交期间仍有并发问题,所以要把锁括住整个业务函数。
基本解决一人一单问题
2.2.5事务失效
spring 事务失效的 12 种场景_spring 截获duplicatekeyexception 不抛异常-CSDN博客
Spring同一个Service类非事务方法调用事务方法事务失效解决方案-CSDN博客
java代理对象_java 什么是代理对象-CSDN博客
但是该代码调用事务业务的时候的this不能处理事务,所以有事务失效的可能。所以要拿当前对象的代理对象