目录
今天我要和大家分享的是如何在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