超卖问题(仅供自己参考)

问题描述:

        在一定的库存下,发放之后导致最后的库存不为0(在高并发的情况下)

问题分析:

        在一定的高并发的情况下,如果不加锁,干什么的,在我们判断最后的库存是否为0的时候,就会导致在库存为0之前,就有好多的线程拿到的库存是不为0之前的值,这就会导致库存的值在为0后还会修改。

问题解决的方法:

1、加锁,读写锁:

        代码如下:

public Result secondKillVoucher(Long voucherId) {

        //思想:这段代码是读写锁来解决超卖问题。
        //首先是判断是否时间是否过期,之后判断库存是否为0
        //之后获取写锁,写锁只能有一个线程来访问
        //获得写锁之后再次判断库存是否为0,做二次检查
        //之后就是对数据库中的库存进行修改了
        VoucherOrder voucherOrder;    //优惠券订单
        SeckillVoucher secondVoucher;   //秒杀优惠券
        secondVoucher = iSeckillVoucherService.getById(voucherId);  //获取相应的秒杀优惠券
        LocalDateTime beginTime = secondVoucher.getBeginTime(); //优惠券开始时间
        LocalDateTime endTime = secondVoucher.getEndTime();     //优惠券结束时间
        if(beginTime.isAfter(LocalDateTime.now())) //判断是否开始
        {
            Result.fail("优惠券抢购时间还未开始");
        }
        if(endTime.isBefore(LocalDateTime.now()))   //判断是否结束
        {
            Result.fail("优惠卷抢购时间结束,谢谢您的参与");
        }
        Integer stock = secondVoucher.getStock();   //代表库存

        if(stock<=0)
        {
            return Result.fail("优惠券数量已经抢购完毕,谢谢您的参与");
        }


        lock.writeLock().lock();
        try{
            secondVoucher = iSeckillVoucherService.getById(voucherId);
            stock = secondVoucher.getStock();
            if(stock<=0)
            {
                return Result.fail("优惠券数量已经抢购完毕,谢谢您的参与");
            }
            secondVoucher.setStock(stock-1);
            if (!iSeckillVoucherService.updateById(secondVoucher)) {
                return Result.fail("优惠券发放完毕");
            }
        }finally {
            lock.writeLock().unlock();
        }
        voucherOrder= new VoucherOrder();
        voucherOrder.setId(redisIdWorker.generateWorkId("cc"));
        voucherOrder.setVoucherId(voucherId);
        voucherOrder.setUserId(1L);
        this.save(voucherOrder);
        return Result.ok("优惠券抢购成功");
    }

2、乐观锁:

        (1):使用version(版本号)

                   就是在数据库表中多增加一个version字段。实现的话看下面

        (2):CAS使用要改变的字段(stock),通过在改变库存值的时候,看前面获取的stock值,和现在数据库中的stock值是否相同,相同就说明没有改变,不同则说明已经有其他线程已经改变了值。

          cas会导致很多数据失败(成功率很低),因为这里使用的库存,我们直接使用当库存大于0的时候都可以修改,这样就可以使成功率上升。

代码如下:

 @Override
    @Transactional
    public Result secondKillVoucher(Long voucherId) {

        //通过乐观锁来实现
        SeckillVoucher voucher = iSeckillVoucherService.getById(voucherId);

        LocalDateTime beginTime = voucher.getBeginTime();
        if(beginTime.isAfter(LocalDateTime.now()))
        {
            return Result.fail("活动还没开始");

        }

        LocalDateTime endTime = voucher.getEndTime();
        if(endTime.isBefore(LocalDateTime.now()))
        {
            return Result.fail("活动已经结束");
        }

        Integer stock = voucher.getStock();
        if(stock<1)
        {
            return Result.fail("优惠券已经发放完成");
        }

        //使用的是乐观锁的话就会有一个问题,导致在高并发的情况下有很多的线程失败,代码如下
        //如果是新增一个字段的version来实现,则在.eq("stock", stock).update(); 改为.eq("version", version).update(),version是前面获取的
        //boolean update = iSeckillVoucherService.update()
        //        .setSql("stock = stock-1")
        //        .eq("voucher_id", voucherId)
        //        .eq("stock", stock).update();
        //if(!update)
        //{
        //    return Result.fail("优惠券发放完毕");
        //}
        
        //为了解决乐观锁的失败的情况,会采取一种方法
        boolean update = iSeckillVoucherService.update()
                .setSql("stock = stock-1")
                .eq("voucher_id", voucherId)
                .gt("stock", 0).update();   //只要在往数据库更新数据时,判断当前的库存是否大于0,大于0就执行,不大于0就返回失败
        if(!update)
        {
            return Result.fail("优惠券发放完毕");
        }

        VoucherOrder voucherOrder = new VoucherOrder();
        voucherOrder.setId(redisIdWorker.generateWorkId("cc"));
        voucherOrder.setVoucherId(voucherId);
        voucherOrder.setUserId(1L);
        this.save(voucherOrder);
        return Result.ok("优惠券抢购成功");
    }

悲观锁和乐观锁总结:

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值