关注

乐观锁与悲观锁:概念、实现与应用场景


乐观锁与悲观锁:概念、实现与应用场景

引言

在多线程编程和并发控制中,锁机制是保证数据一致性的重要手段。根据对并发冲突的处理方式,锁可以分为 乐观锁悲观锁 两种类型。本文将详细介绍乐观锁和悲观锁的概念、实现方式以及适用场景,帮助读者更好地理解并选择合适的锁机制。


1. 悲观锁(Pessimistic Locking)

1.1 定义

悲观锁是一种保守的并发控制策略,它假设在数据访问过程中一定会发生冲突,因此在访问数据时会对数据进行加锁,确保其他线程无法修改数据。

1.2 特点
  • 加锁时机:在访问数据时立即加锁。
  • 适用场景:写操作多、读操作少的场景。
  • 优点:保证数据强一致性,避免脏读和不可重复读。
  • 缺点:加锁会降低并发性能,可能导致线程阻塞。
1.3 实现方式
  • 数据库:使用 SELECT ... FOR UPDATE 语句锁定数据行。
    BEGIN;
    SELECT * FROM table WHERE id = 1 FOR UPDATE;
    UPDATE table SET value = 100 WHERE id = 1;
    COMMIT;
    
  • Java:使用 synchronized 关键字或 ReentrantLock
    synchronized (lock) {
        // 访问共享资源
    }
    
1.4 适用场景
  • 高冲突场景:当多个线程频繁修改同一数据时,悲观锁可以有效避免数据不一致。
  • 强一致性要求:如银行转账、库存扣减等场景。

2. 乐观锁(Optimistic Locking)

2.1 定义

乐观锁是一种乐观的并发控制策略,它假设在数据访问过程中不会发生冲突,因此在访问数据时不会加锁,而是在提交更新时检查数据是否被修改。

2.2 特点
  • 加锁时机:在提交更新时检查冲突。
  • 适用场景:读操作多、写操作少的场景。
  • 优点:提高并发性能,减少线程阻塞。
  • 缺点:需要处理冲突,可能导致更新失败。
2.3 实现方式
  • 数据库:使用版本号(Version)或时间戳(Timestamp)实现。

    UPDATE table SET value = 100, version = version + 1 WHERE id = 1 AND version = 2;
    

    如果更新失败,说明数据已被其他线程修改,需要重试或处理冲突。

  • Java:使用 Atomic 类或 CAS(Compare And Swap)操作。

    AtomicInteger version = new AtomicInteger(0);
    int currentVersion = version.get();
    // 假设 value 是需要更新的共享变量
    if (version.compareAndSet(currentVersion, currentVersion + 1)) {
        // 更新成功
    } else {
        // 更新失败,处理冲突
    }
    
2.4 适用场景
  • 低冲突场景:当多个线程很少修改同一数据时,乐观锁可以提高并发性能。
  • 高并发读操作:如新闻网站的文章阅读、社交媒体的点赞等场景。

3. 乐观锁与悲观锁的对比

特性悲观锁乐观锁
加锁时机访问数据时立即加锁提交更新时检查冲突
适用场景写操作多、读操作少读操作多、写操作少
优点保证数据强一致性提高并发性能
缺点加锁降低并发性能,可能导致线程阻塞需要处理冲突,可能导致更新失败
实现方式synchronizedReentrantLockFOR UPDATE版本号、时间戳、CAS 操作

4. 实际应用中的选择

4.1 如何选择锁机制
  • 悲观锁:适用于写操作频繁、冲突概率高的场景,如订单系统、库存管理。
  • 乐观锁:适用于读操作频繁、冲突概率低的场景,如新闻阅读、点赞系统。
4.2 混合使用

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

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

5. 代码示例

5.1 悲观锁示例
public class PessimisticLockExample {
    private int balance = 100;

    public synchronized void withdraw(int amount) {
        if (balance >= amount) {
            balance -= amount;
            System.out.println("Withdrawal successful. Remaining balance: " + balance);
        } else {
            System.out.println("Insufficient balance.");
        }
    }
}
5.2 乐观锁示例
public class OptimisticLockExample {
    private AtomicInteger balance = new AtomicInteger(100);

    public void withdraw(int amount) {
        while (true) {
            int currentBalance = balance.get();
            if (currentBalance >= amount) {
                if (balance.compareAndSet(currentBalance, currentBalance - amount)) {
                    System.out.println("Withdrawal successful. Remaining balance: " + balance.get());
                    break;
                }
            } else {
                System.out.println("Insufficient balance.");
                break;
            }
        }
    }
}

6. 总结

  • 悲观锁:通过加锁保证数据一致性,适用于高冲突场景。
  • 乐观锁:通过冲突检测提高并发性能,适用于低冲突场景。
  • 选择依据:根据业务场景的特点选择合适的锁机制,必要时可以混合使用。

通过理解乐观锁和悲观锁的特点及适用场景,开发者可以更好地设计并发控制策略,提升系统的性能和稳定性。


参考资料
  • Java 官方文档:synchronizedAtomic
  • 《Java 并发编程实战》
  • 《高性能 MySQL》

转载自CSDN-专业IT技术社区

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/weixin_46146718/article/details/145774727

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--