什么是悲观锁和乐观锁

悲观锁和乐观锁是两种常见的并发控制策略,用于解决多线程或多进程环境下共享资源访问的冲突问题。它们基于不同的假设和实现方式,适用于不同的业务场景。

悲观锁(Pessimistic Locking)

基本概念

悲观锁是一种"悲观"的并发控制机制,它假设在多线程环境下,数据冲突是常态而非例外。因此在访问数据前,悲观锁会先获取锁,确保在持有锁的期间内,其他线程无法修改数据,从而避免并发冲突。

悲观锁的核心思想可以概括为:"宁可错杀一千,不可放过一个"。它在数据处理前就假设最坏的情况会发生,因此采取预防性的加锁措施。

主要特点

  • 排他性:一旦某个线程获得锁,其他线程必须等待
  • 阻塞性:获取锁失败的线程会被阻塞,直到锁被释放
  • 一致性保证强:能有效避免脏读、不可重复读等问题
  • 开销较大:加锁、释放锁需要额外资源
  • 可能引发死锁:不正确的使用可能导致死锁情况

实现方式

在Java中,悲观锁主要通过以下方式实现:

  1. synchronized关键字​:Java内置的同步机制

    typescript 复制代码
    // 同步方法
    public synchronized void increment() {
        count++;
    }
    
    // 同步代码块
    synchronized(lock) {
        count++;
    }
  2. ReentrantLock类​:Java并发包中的显式锁

    csharp 复制代码
    private final ReentrantLock lock = new ReentrantLock();
    
    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }
  3. 数据库悲观锁 ​:如SELECT ... FOR UPDATE语句

    sql 复制代码
    SELECT * FROM accounts WHERE id = 1 FOR UPDATE;

适用场景

悲观锁最适合以下场景:

  • 临界区执行时间长:当操作需要较长时间完成时
  • 冲突频率高:当并发修改的概率很高时
  • 强一致性要求:需要严格保证数据一致性的场景
  • 写操作多:写多读少的场景

典型应用场景包括:

  • 银行转账
  • 库存扣减
  • 订单处理
  • 票务系统

乐观锁(Optimistic Locking)

基本概念

乐观锁是一种并发控制机制,它假设在大多数情况下,数据冲突的概率很低,因此在读取数据时不加锁,只在更新数据时检查是否有其他事务修改了数据。如果数据在读取后被其他事务修改,则更新失败,需要重新读取数据并重试更新操作。

乐观锁的核心思想是通过版本号或时间戳来记录数据的变更历史。每次更新数据时,都会检查版本号或时间戳是否与读取时的值一致。如果不一致,说明数据已经被其他事务修改,当前事务需要回滚并重试。

主要特点

  • 无锁读取:读取数据时不加锁,提高并发性能
  • 冲突检测:只在更新时检查数据是否被修改
  • 重试机制:冲突时需要重试操作
  • 适合读多写少:在低冲突场景下性能优越

实现方式

乐观锁可以通过以下几种方式实现:

  1. 版本号机制​:在数据表中增加一个版本号字段,每次更新数据时,版本号加1

    ini 复制代码
    UPDATE products SET stock = stock - 1, version = version + 1 
    WHERE id = 1 AND version = 2;
  2. 时间戳机制​:使用时间戳代替版本号,原理相同

    sql 复制代码
    UPDATE users SET name = 'newName', timestamp = CURRENT_TIMESTAMP 
    WHERE id = 1 AND timestamp = '2023-01-01 12:00:00';
  3. CAS(Compare And Swap)算法​:Java中的原子类实现

    scss 复制代码
    AtomicInteger version = new AtomicInteger(0);
    int current = version.get();
    if(version.compareAndSet(current, current + 1)) {
        // 更新成功
    } else {
        // 更新失败,重试
    }

适用场景

乐观锁适用于读多写少的场景,特别是当冲突发生概率较低时效果最佳。例如:

  • 电商系统中的库存管理:用户浏览商品时读取库存,下单时检查库存是否有变化
  • 社交网络中的消息系统:用户读取消息列表时不需要加锁,发送新消息时检查是否有其他用户修改了消息状态

悲观锁与乐观锁的对比

特性 悲观锁 乐观锁
假设 冲突会发生 冲突很少发生
实现方式 加锁(synchronized, ReentrantLock) 版本号/时间戳/CAS
并发性
开销
适用场景 写多读少 读多写少
冲突处理 阻塞等待 回滚重试
示例 SELECT ... FOR UPDATE AtomicInteger, 版本控制
优点 强一致性保证 高并发性能
缺点 性能开销大,可能死锁 高冲突时频繁重试

选择建议

  1. 悲观锁适用场景​:

    • 数据冲突概率高
    • 对数据一致性要求严格
    • 写操作远多于读操作
    • 操作执行时间长
  2. 乐观锁适用场景​:

    • 数据冲突概率低
    • 读操作远多于写操作
    • 系统需要高并发性能
    • 可以接受偶尔的重试
  3. 混合使用​:

    在实际应用中,可以根据不同的业务场景混合使用悲观锁和乐观锁。例如:

    • 在订单系统中,使用悲观锁保证库存扣减的一致性
    • 在新闻阅读系统中,使用乐观锁提高并发性能

实际应用示例

悲观锁示例:银行转账

csharp 复制代码
public class BankAccount {
    private double balance;
    private final Lock lock = new ReentrantLock();
    
    public void transfer(double amount) {
        lock.lock();
        try {
            double newBalance = balance + amount;
            Thread.sleep(100); // 模拟业务处理耗时
            balance = newBalance;
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

乐观锁示例:库存扣减

java 复制代码
public class Product {
    private int stock;
    private final AtomicInteger version = new AtomicInteger(0);
    
    public boolean deductStock() {
        while (true) {
            int currentVersion = version.get();
            int currentStock = stock;
            if (currentStock <= 0) return false;
            
            if (version.compareAndSet(currentVersion, currentVersion + 1)) {
                stock = currentStock - 1;
                return true;
            }
        }
    }
}

总结

悲观锁和乐观锁是两种互补的并发控制策略,各有优缺点和适用场景。理解它们的核心思想、实现方式和适用场景,可以帮助开发者在实际项目中做出合理的选择,构建高性能且数据一致的系统。

相关推荐
canonical_entropy6 小时前
DDD本质论:从哲学到数学,再到工程实践的完整指南之理论篇
后端·低代码·领域驱动设计
后端小张6 小时前
SpringBoot 控制台秒变炫彩特效,秀翻同事指南!
java·后端
it技术6 小时前
Pytorch项目实战 :基于RNN的实现情感分析
pytorch·后端
235167 小时前
【MySQL】MVCC:从核心原理到幻读解决方案
java·数据库·后端·sql·mysql·缓存
YQ_ZJH7 小时前
Spring Boot 如何校验前端传递的参数
前端·spring boot·后端
普罗米修斯7 小时前
C++ 设计模式理论与实战大全【共73课时】
c++·后端
普罗米修斯7 小时前
C++ 设计模式原理与实战大全-架构师必学课程 | 完结
c++·后端
MX_93598 小时前
SpringBoot项目优先级以及bean的管理
java·spring boot·后端·spring