多线程超卖问题的解决方案:悲观锁与乐观锁
创作时间:
作者:
@小白创作中心
多线程超卖问题的解决方案:悲观锁与乐观锁
引用
CSDN
1.
https://blog.csdn.net/qq_52351978/article/details/136413998
在多线程环境下,如何避免库存超卖问题?本文将从悲观锁和乐观锁两种解决方案入手,详细探讨这一问题的原理和实现方式。
1. 超卖问题
超卖问题通常出现在多线程环境中,当多个线程同时访问和修改共享资源时,可能会导致资源被过度消耗。具体来说,假设线程1查询库存发现库存大于1,正准备扣减库存时,线程2也查询到库存大于1并尝试扣减,最终导致库存被过度扣减。
2. 乐观锁解决方案
乐观锁的基本思想是认为线程冲突不会经常发生,因此在操作数据时不会立即加锁,而是假设操作能够成功。如果在提交数据时发现数据已被其他线程修改,则操作失败并重试。
解决原理
CAS(Compare and Swap)操作是乐观锁的一种实现方式。在多线程环境下,只有第一个执行CAS操作的线程能够成功修改库存信息,其他线程由于版本号不匹配而失败。这种机制可以有效避免多个线程同时修改库存信息的问题。
业务场景
在使用乐观锁时,如果多个线程同时获取到相同的库存值并尝试扣减,只有第一个线程能够成功,其他线程会因为版本号不匹配而失败。例如,如果有100个线程同时获取到库存为100,只有1个线程能成功扣减,其他线程都会失败。
弊端
使用乐观锁时,如果并发量很大,可能会导致很多操作失败,从而降低系统的整体性能。例如,即使库存有100个,但在高并发场景下,由于成功率低,最终库存可能仍然很高。
3. 悲观锁解决方案
悲观锁假设线程冲突经常发生,因此在操作数据前会先获取锁,确保线程串行执行。以下是使用悲观锁解决超卖问题的代码示例:
@Transactional
public Result createVoucherOrder(Long voucherId) {
Long userId = UserHolder.getUser().getId();
// 5.1.查询订单
int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
// 5.2.判断是否存在
if (count > 0) {
// 用户已经购买过了
return Result.fail("用户已经购买过一次!");
}
// 6.扣减库存
boolean success = seckillVoucherService.update()
.setSql("stock = stock - 1") // set stock = stock - 1
.eq("voucher_id", voucherId).gt("stock", 0) // where id = ? and stock > 0
.update();
if (!success) {
// 扣减失败
return Result.fail("库存不足!");
}
// 7.创建订单
VoucherOrder voucherOrder = new VoucherOrder();
// 7.1.订单id
long orderId = redisIdWorker.nextId("order");
voucherOrder.setId(orderId);
// 7.2.用户id
voucherOrder.setUserId(userId);
// 7.3.代金券id
voucherOrder.setVoucherId(voucherId);
save(voucherOrder);
// 7.返回订单id
return Result.ok(orderId);
}
注意事项
- 锁的对象需要调用intern()
- 当使用字符串常量或调用
String类的intern()方法创建字符串时,会将字符串添加到字符串常量池中。而使用new String()创建字符串时,会在堆内存中创建一个新的字符串对象。 - 在多线程环境下,如果多个线程同时访问到相同的字符串,但创建了不同的字符串对象,会导致锁失效的问题。因此,需要确保锁的对象是同一个实例。
- 事务管理
- 如果将业务封装在A方法并在A方法使用锁,在B方法调用A方法的情况下,可能会在线程1释放锁之后提交事务的瞬间出现线程2获得锁并在线程1提交事务之前售卖库存的情况,导致超卖。因此最好在B方法调用A方法且将A整个锁住。
- 若A与B方法都在同一个类中且B调用A的情况下,需要通过手动获取动态代理对象去提交B的事务。这是因为当在同一个类中调用方法时,不会触发Spring的代理机制,导致事务注解
@Transactional无效。为了解决这个问题,可以使用AopContext.currentProxy()来获取代理对象,通过代理对象调用方法,这样事务注解才会生效。
- 使用AopContext获得代理对象
- 需要在启动类上添加
@EnableAspectJAutoProxy(exposeProxy = true)注解,以启用AspectJ自动代理并暴露代理对象。
通过以上分析可以看出,悲观锁和乐观锁各有优劣。在实际应用中,需要根据具体场景选择合适的解决方案。对于库存扣减等高并发场景,通常推荐使用悲观锁以确保数据一致性。
热门推荐
J1豁免DIY保姆级图解,手把手教你办J1 Waiver(不持异议信版)
全球AI治理再升级:外滩大会强调人工智能伦理的重要性
布伦森55+9双队史第二!尼克斯加时险胜奇才7连胜 唐斯30+14
天谴的解释:跨越文化的超自然惩罚观念
蔡文姬的生平经历:乱世才女的悲歌
三国最有才的女子:蔡文姬的卓越才华
如何挑选薏米以及如何把薏米磨成粉
健康管理师职业资格证的考试内容有哪些?
一文看懂Mamba,Transformer最强竞争者
PLC编程语言有几种
智商与智力的区别
瑜伽垫尺寸与厚度选购指南:初学者必读
不适合的眼睛度数会增加散光吗
合同中的FOB条款:定义、法律意义及实务操作
2025年美国公司报税全攻略:从税种到流程,全方位解析税务优化
城乡居民养老保险,咋交最省钱又划算?规划缴纳的小妙招!
水瓶座女人的十大特点 水瓶女的8大弱点
实时路径追踪——D5渲染器全新实时 GI 解决方案
掌握基础护肤知识,打造健康美丽肌肤
《刺客信条:影》战斗系统深度解析:攻击、防御与敌人应对策略
关于欧洲穆斯林人口的五个真相
孙准浩慌了!面临全球禁赛,被金敬道拖下水,踢假球毁掉职业生涯
哪些激素与减肥有关
如何分析股票的投资者行为
解析35份非农报告,黄金如何交易?需要留意前值修正情况
顺德五道必尝名菜:从均安蒸猪到顺德鱼生,每一道都是经典
内务府总管的财富:从法律角度解析权力与财产的关系
AI会是第四次革命吗?
鲤鲫鱼是什么鱼?:中科院培育的四倍体杂交鱼种
太阳穴长痘痘怎么办?专业医生给出五大处理方法