乐观锁处理秒杀超卖问题及悲观锁解决一人一单

@Service
@Transactional//事务控制
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {

    @Resource
    SeckillVoucherServiceImpl seckillVoucherService;
    @Resource
    RedisIdWorker redisIdWorker;
    @Resource
    VoucherOrderMapper voucherOrderMapper;
    @Override
    public Result seckillVoucher(Long voucherId) {
        //1.查询优惠券
        SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
        //2.判断秒杀是否开始
        if(voucher.getBeginTime().isAfter(LocalDateTime.now())){
            return Result.fail("秒杀尚未开始");
        }
        //3.判断秒杀是否结束
        if(voucher.getEndTime().isBefore(LocalDateTime.now())){
            return Result.fail("秒杀已经结束");
        }
        //4.判断是否还有库存
        if(voucher.getStock()< 1){
            return Result.fail("库存不足");
        }
        //5.扣减库存
        boolean success=seckillVoucherService.update().
                setSql("stock = stock-1")//执行手写SQL
                .eq("voucher_id",voucherId).update();//Where条件
        if(!success){
            //扣减失败
            return Result.fail("库存不足");
        }
        //6.创建订单
        VoucherOrder voucherOrder = new VoucherOrder();
        //6.1订单ID
        long orderId=redisIdWorker.nextId("order");
        voucherOrder.setId(orderId);
        //6.2.用户ID
        Long userId=UserHolder.getUser().getId();
        voucherOrder.setUserId(userId);
        //6.3.代金卷ID
        voucherOrder.setVoucherId(voucherId);
        //6.4.写入数据库
        save(voucherOrder);
        //7.返回订单号
        return Result.ok(orderId);
    }
}

上述代码会出现超卖问题,

 

悲观锁:

悲观锁:认为线程安全问题一定会发生,因此在进行数据库操作之前先获取锁,确保线程串行执行 
      
*Synchronized ,
*Lock都属于悲观锁

乐观锁:

线程安全问题不一定发生,因此不加锁,在更新数据时判断有没有其他线程对数据进行了修改
*若没有修改,则认为是安全的,自己更新数据
*若已经被其他线程修改说明已发生安全问题,此时可以重试或异常

实现乐观锁的关键:判断之前查询得到的数据是否被修改过

1、版本号法,添加version字段 

2.CAS法把库存代替版本号(太聪明了)

  在执行数据库操作时,增加条件stock > 0

 boolean success=seckillVoucherService.update().
                setSql("stock = stock-1")//set stock = stock-1
                .eq("voucher_id",voucherId)//where voucher_id=?
//                .eq("stock",voucher.getStock())//where stock=?成功添加乐观锁,但失败率变大
                .gt("stock",0)//where stack>0,此处有mysql操作自带的锁保证线程安全 
                .update();

一人一单:

启动类上加暴露代理对象的注解

@MapperScan("com.hmdp.mapper")
@SpringBootApplication
@EnableAspectJAutoProxy(exposeProxy = true)  //暴露代理对象
public class HmDianPingApplication {
    public static void main(String[] args) {
        SpringApplication.run(HmDianPingApplication.class, args);
    }
}

实现一人一单

 @Override
    public Result seckillVoucher(Long voucherId) {
        //1.查询优惠券
        SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
        //2.判断秒杀是否开始
        if(voucher.getBeginTime().isAfter(LocalDateTime.now())){
            return Result.fail("秒杀尚未开始");
        }
        //3.判断秒杀是否结束
        if(voucher.getEndTime().isBefore(LocalDateTime.now())){
            return Result.fail("秒杀已经结束");
        }
        //4.判断是否还有库存
        if(voucher.getStock()< 1){
            return Result.fail("库存不足");
        }
        Long userId = UserHolder.getUser().getId();
        //定义同步代码块,userId作为锁对象
        //使用intern方法可以确保所有相同的 userId 都使用同一个字符串对象作为锁
        //将锁添加在整个方法上,防止先释放锁,后提交业务



        //synchronized是JVM的本地锁,若为分布式应用,需要分布式锁
        synchronized (userId.toString().intern()) {
            //获取代理对象(事务),使得事务得以生效
                IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
            return proxy.createVoucherOrder(voucherId);
        }
    }
    @Transactional//确保本方法中的数据库操作在同一个事务中
    public Result createVoucherOrder(Long voucherId) {
        //5.一人一单(需要加锁实现)
        Long userId = UserHolder.getUser().getId();
        synchronized (userId.toString().intern()) {
            //5.1.查询订单
            int count = query()
                    .eq("user_id", userId)//where user_id=?
                    .eq("voucher_id", voucherId).count();
            //5.2.判断是否已经存在
            if (count > 0) {
                return Result.fail("本优惠卷每个用户仅限一单");
            }
            //6.扣减库存,添加乐观锁,处理超卖问题
            boolean success = seckillVoucherService.update().
                    setSql("stock = stock-1")//set stock = stock-1
                    .eq("voucher_id", voucherId)//where voucher_id=?
                    //.eq("stock",voucher.getStock())//where stock=?成功添加乐观锁,但失败率变大
                    .gt("stock", 0)//where stack>0,此处有mysql操作自带的锁保证线程安全
                    .update();
            if (!success) {
                //扣减失败
                return Result.fail("库存不足");
            }
            //7.创建订单
            VoucherOrder voucherOrder = new VoucherOrder();
            //7.1订单ID
            long orderId = redisIdWorker.nextId("order");
            voucherOrder.setId(orderId);
            //7.2.用户ID
            voucherOrder.setUserId(userId);
            //7.3.代金卷ID
            voucherOrder.setVoucherId(voucherId);
            //7.4.写入数据库
            save(voucherOrder);
            //8.返回订单号
            return Result.ok(orderId);
        }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值