外观
悲观锁和乐观锁使用场景?
⭐ 题目日期:
阿里 - 2025/4/22,美团 - 2024/12/23
📝 题解:
悲观锁与乐观锁的使用场景详解
1. 核心概念对比
特性 | 悲观锁 | 乐观锁 |
---|---|---|
核心思想 | 假设并发冲突必然发生,提前加锁 | 假设并发冲突很少发生,更新时检测冲突 |
实现方式 | 数据库行锁(SELECT ... FOR UPDATE )、Java的synchronized 、ReentrantLock | 版本号(Version)、时间戳、CAS(Compare And Swap) |
性能开销 | 高(锁竞争、阻塞) | 低(无锁,冲突时重试) |
适用场景 | 写操作多、冲突频繁 | 读操作多、冲突较少 |
2. 悲观锁使用场景
特点:通过显式加锁确保独占访问,适用于强一致性要求的场景。
典型场景:
金融交易(如转账)
- 需求:同一账户并发扣款时,需严格保证余额正确。
- 实现:数据库事务中使用
SELECT ... FOR UPDATE
锁定账户行。
BEGIN; SELECT balance FROM accounts WHERE id = 1 FOR UPDATE; -- 加悲观锁 UPDATE accounts SET balance = balance - 100 WHERE id = 1; COMMIT;
库存扣减(高并发抢购)
- 需求:避免超卖,确保库存不为负。
- 实现:在扣减库存前锁定商品记录。
synchronized (productLock) { if (product.getStock() > 0) { product.setStock(product.getStock() - 1); } }
订单状态变更
- 需求:防止订单重复支付或取消。
- 实现:更新订单前锁定订单行,确保状态唯一性。
3. 乐观锁使用场景
特点:通过冲突检测(如版本号)实现无锁并发,适用于高吞吐、低冲突的场景。
典型场景:
文章点赞/阅读量统计
- 需求:高频轻量级更新,允许少量误差。
- 实现:使用Redis的
INCR
(原子操作)或数据库版本号。
UPDATE articles SET likes = likes + 1, version = version + 1 WHERE id = 123 AND version = 5; -- 版本号校验
分布式配置更新
- 需求:多节点并发更新配置,需最终一致。
- 实现:CAS操作更新版本号。
Config config = getConfig(); int currentVersion = config.getVersion(); boolean success = updateConfig(config.getId(), newValue, currentVersion); if (!success) { retry(); // 冲突时重试 }
购物车商品修改
- 需求:用户频繁修改购物车,冲突概率低。
- 实现:前端提交数据时携带版本号,后端校验更新。
{ "cartId": 456, "items": [...], "version": 3 // 乐观锁版本 }
4. 选择依据
考虑因素 | 悲观锁 | 乐观锁 |
---|---|---|
冲突频率 | 高 | 低 |
数据一致性要求 | 强一致(如金融系统) | 最终一致(如社交应用) |
性能需求 | 容忍较低吞吐量 | 追求高并发、低延迟 |
实现复杂度 | 简单(直接加锁) | 需处理重试/补偿逻辑 |
5. 实现技术示例
悲观锁实现(数据库行锁):
-- MySQL示例
START TRANSACTION;
SELECT * FROM products WHERE id = 1 FOR UPDATE; -- 加锁
UPDATE products SET stock = stock - 1 WHERE id = 1;
COMMIT;
乐观锁实现(Java CAS):
public class OptimisticLockExample {
private AtomicInteger version = new AtomicInteger(0);
public void updateData() {
int currentVersion = version.get();
// 模拟业务计算
boolean success = version.compareAndSet(currentVersion, currentVersion + 1);
if (!success) {
// 重试或抛出异常
throw new ConcurrentModificationException("数据已被修改");
}
}
}
乐观锁实现(数据库版本号):
UPDATE user_profile
SET name = 'Alice', version = version + 1
WHERE id = 100 AND version = 2; -- 版本号校验
6. 常见问题与解决方案
问题 | 悲观锁 | 乐观锁 |
---|---|---|
死锁 | 设置锁超时、按固定顺序加锁 | 无死锁风险 |
ABA问题 | 不适用 | 使用递增版本号替代时间戳 |
高重试开销 | 无 | 限制重试次数或降级处理 |
7. 总结
- 悲观锁:适合短事务、高冲突场景,用锁换安全。
典型场景:银行转账、库存扣减、订单状态变更。 - 乐观锁:适合长事务、低冲突场景,用重试换性能。
典型场景:计数器更新、配置管理、多版本并发控制(MVCC)。
最终建议:
- 在强一致性要求下选择悲观锁。
- 在高并发、低冲突场景优先使用乐观锁,结合熔断/降级机制处理冲突。