今天我要和大家分享的是如何在Java中使用乐观锁和CAS(Compare-And-Swap)技术来解决超卖的问题。最近我在项目中实现了这个功能,觉得非常有意思,所以决定分享出来。希望对大家有所帮助!
背景介绍
秒杀活动通常在电商平台中很常见,我觉得实现这个功能的难点在于多线程避免超卖。为了应对这个问题,我们使用了乐观锁和CAS法,这样也不至于像悲观锁一样串行,而且兼顾了高并发的性能。
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
@Resource
private ISeckillVoucherService seckillVoucherService;
@Resource
private RedisIdWorker redisIdWorker;
@Override
@Transactional
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.扣减库存 - 使用乐观锁和CAS
boolean success = seckillVoucherService.update()
.setSql("stock = stock - 1")
.eq("voucher_id", voucherId)
.gt("stock", 0) // 防止超卖
.update();
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 id = UserHolder.getUser().getId();
voucherOrder.setUserId(id);
// 6.3.设置优惠券id
voucherOrder.setVoucherId(voucherId);
save(voucherOrder);
// 7.返回订单id
return Result.ok(orderId);
}
}
用乐观锁和CAS扣减库存:这里使用了MyBatis Plus的update
方法,通过设置setSql("stock = stock - 1")
和条件eq("voucher_id", voucherId)
及gt("stock", 0)
来确保在扣减库存时不会发生超卖。CAS确保了操作的原子性,这让并发情况下库存扣减的操作更加可靠。
最后通过Jmeter测试,异常率为0
标签:Result,CAS,Overselling,voucher,超卖,id,stock From: https://blog.csdn.net/Takumilove/article/details/139621838