问题描述:
在一定的库存下,发放之后导致最后的库存不为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("优惠券抢购成功");
}
悲观锁和乐观锁总结: