// 获取优惠券
SeckillVoucher seckillVoucher = seckillVoucherService.getById(voucherId);
// 判断库存是否充足
if (seckillVoucher.getStock() <= 0) {
return Result.fail("库存不足!");
}
// 更新库存
boolean success = seckillVoucherService.update()
.setSql("stock = stock - 1")
.eq("voucher_id", voucherId)
.update();上述代码中获取和更新操作是分开的,不是原子性操作,因此可能出现只剩1张优惠券,但几十个线程同时获取到优惠券,此时都判断库存充足,导致库存被扣减几十次变为负数,产生超卖问题。
解决方法
可以使用加锁的方法解决该问题。
悲观锁
悲观锁从获取优惠券之前到更新库存后都锁起来,因此该代码块不再并发,自然不存在超卖问题。
synchronized (lock) {
// 获取优惠券
SeckillVoucher seckillVoucher = seckillVoucherService.getById(voucherId);
// 判断库存是否充足
if (seckillVoucher.getStock() <= 0) {
return Result.fail("库存不足!");
}
// 更新库存
boolean success = seckillVoucherService.update()
.setSql("stock = stock - 1")
.eq("voucher_id", voucherId)
.update();
}乐观锁
乐观锁在更新库存时检查版本号是否一致,只有版本号没有被修改的才更新。
// 获取优惠券
SeckillVoucher seckillVoucher = seckillVoucherService.getById(voucherId);
// 判断库存是否充足
if (seckillVoucher.getStock() <= 0) {
return Result.fail("库存不足!");
}
// 更新库存
boolean success = seckillVoucherService.update()
.setSql("stock = stock - 1")
.eq("voucher_id", voucherId)
.eq("version", seckillVoucher.getVersion())
.update();这里也可以使用库存stock作为版本号,
boolean success = seckillVoucherService.update()
.setSql("stock = stock - 1")
.eq("voucher_id", voucherId)
.eq("stock", seckillVoucher.getStock())
.update();但是这样成功率会下降,因为有许多线程会出现版本号不一致的情况,注意到条件可以改成stock > 0,从而提高性能。
boolean success = seckillVoucherService.update()
.setSql("stock = stock - 1")
.eq("voucher_id", voucherId)
.gt("stock", 0)
.update();