Skip to content

悲观锁和乐观锁使用场景?

约 999 字大约 3 分钟

多线程与并发美团阿里

2025-04-27

⭐ 题目日期:

阿里 - 2025/4/22,美团 - 2024/12/23

📝 题解:

悲观锁与乐观锁的使用场景详解


1. 核心概念对比

特性悲观锁乐观锁
核心思想假设并发冲突必然发生,提前加锁假设并发冲突很少发生,更新时检测冲突
实现方式数据库行锁(SELECT ... FOR UPDATE)、Java的synchronizedReentrantLock版本号(Version)、时间戳、CAS(Compare And Swap)
性能开销高(锁竞争、阻塞)低(无锁,冲突时重试)
适用场景写操作多、冲突频繁读操作多、冲突较少

2. 悲观锁使用场景

特点:通过显式加锁确保独占访问,适用于强一致性要求的场景。

典型场景
  1. 金融交易(如转账)

    • 需求:同一账户并发扣款时,需严格保证余额正确。
    • 实现:数据库事务中使用SELECT ... FOR UPDATE锁定账户行。
    BEGIN;
    SELECT balance FROM accounts WHERE id = 1 FOR UPDATE; -- 加悲观锁
    UPDATE accounts SET balance = balance - 100 WHERE id = 1;
    COMMIT;
  2. 库存扣减(高并发抢购)

    • 需求:避免超卖,确保库存不为负。
    • 实现:在扣减库存前锁定商品记录。
    synchronized (productLock) {
        if (product.getStock() > 0) {
            product.setStock(product.getStock() - 1);
        }
    }
  3. 订单状态变更

    • 需求:防止订单重复支付或取消。
    • 实现:更新订单前锁定订单行,确保状态唯一性。

3. 乐观锁使用场景

特点:通过冲突检测(如版本号)实现无锁并发,适用于高吞吐、低冲突的场景。

典型场景
  1. 文章点赞/阅读量统计

    • 需求:高频轻量级更新,允许少量误差。
    • 实现:使用Redis的INCR(原子操作)或数据库版本号。
    UPDATE articles 
    SET likes = likes + 1, version = version + 1 
    WHERE id = 123 AND version = 5; -- 版本号校验
  2. 分布式配置更新

    • 需求:多节点并发更新配置,需最终一致。
    • 实现:CAS操作更新版本号。
    Config config = getConfig();
    int currentVersion = config.getVersion();
    boolean success = updateConfig(config.getId(), newValue, currentVersion);
    if (!success) {
        retry(); // 冲突时重试
    }
  3. 购物车商品修改

    • 需求:用户频繁修改购物车,冲突概率低。
    • 实现:前端提交数据时携带版本号,后端校验更新。
    {
        "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)。

最终建议

  • 强一致性要求下选择悲观锁。
  • 高并发、低冲突场景优先使用乐观锁,结合熔断/降级机制处理冲突。