乐观锁与悲观锁

简介: 乐观锁与悲观锁

目录

乐观锁

实现乐观锁的方式

1. 版本号机制:

2. 时间戳(Timestamp)机制:

3. CAS(Compare and Swap)操作:

乐观锁的优点

乐观锁的缺点

使用乐观锁的示例(基于版本号机制):

悲观锁

实现悲观锁的方式

1. 数据库锁:

2. 编程语言级别的锁:

3. 互斥量(Mutex):

悲观锁的优点

悲观锁的缺点

使用悲观锁的示例:


乐观锁


       乐观锁是一种并发控制的机制,其核心思想是假设多个事务之间的冲突是不太可能发生的,因此在事务处理之前不会加锁,而是在事务提交的时候再检查是否有冲突。如果发现冲突,就会回滚事务,重新尝试。

实现乐观锁的方式

1. 版本号机制:
  • 每个数据记录都关联一个版本号,当读取数据时,将版本号一同读出。在更新数据时,只有当版本号匹配时才能执行更新操作,否则认为是冲突,需要进行回滚或其他处理。
  • 适用于数据库表中的记录,常用于数据库乐观锁实现。
2. 时间戳(Timestamp)机制:
  • 每个事务执行时都记录一个时间戳,更新数据时带上时间戳。当提交时,检查时间戳,如果发现其他事务已经更新了数据,就认为发生了冲突。
  • 可以在数据库中记录事务的开始时间作为时间戳,也可以使用递增的整数作为版本号。
3. CAS(Compare and Swap)操作:
  • 使用原子性的CAS操作来判断是否发生冲突。在Java中,Atomic 类提供了一些原子操作,如 compareAndSet
  • 适用于基于内存的数据结构,如Java中的AtomicIntegerAtomicLong等。

乐观锁的优点

  1. 性能好: 在低并发环境下,乐观锁的性能通常优于悲观锁,因为不需要额外的加锁和解锁操作。
  2. 无阻塞: 由于乐观锁不会一开始就阻塞线程,因此适用于读操作较频繁、写操作较少的场景。

乐观锁的缺点

  1. 冲突处理: 当多个事务发生冲突时,需要进行冲突处理,通常是通过回滚事务,重新尝试。
  2. 不适用于高并发写操作: 当写操作较频繁时,乐观锁的性能可能下降,因为不断的冲突会导致事务的回滚和重试。
  3. 无法解决所有并发问题: 乐观锁机制不能解决所有并发问题,特别是在一些复杂的业务场景中。

使用乐观锁的示例(基于版本号机制):

class Account {
    private String accountId;
    private double balance;
    private long version; // 版本号
    // 省略其他代码
    // 更新余额的方法
    public void updateBalance(double amount) {
        // 模拟乐观锁检查
        if (version != getAccountVersionFromDatabase()) {
            throw new OptimisticLockException("Concurrent modification detected");
        }
        // 更新余额
        this.balance += amount;
        
        // 更新版本号
        version++;
        
        // 更新数据库中的版本号和余额
        updateAccountInDatabase();
    }
}

  version 是账户对象的版本号,每次更新时都需要检查数据库中的版本号是否一致,如果不一致,则抛出乐观锁异常。

悲观锁


       悲观锁是一种并发控制的机制,它的核心思想是在操作数据之前,悲观地认为会有并发操作的冲突,因此先进行加锁,确保每个时刻只有一个事务可以访问或修改共享资源。这种锁定机制确保了数据的一致性,但也可能导致性能的下降,因为多个事务可能需要等待锁的释放。

       悲观锁的实现方式主要包括数据库锁、行级锁、表级锁等,以及编程语言级别的锁,如Java中的synchronized关键字、数据库中的SELECT ... FOR UPDATE等。

实现悲观锁的方式

1. 数据库锁:
  • 行级锁(Row-level lock): 在数据库中锁定表中的某一行,确保只有一个事务可以修改这一行的数据。例如,在SQL中可以使用FOR UPDATE语句。
  • 表级锁(Table-level lock): 锁定整个表,防止其他事务访问该表中的任何数据。
2. 编程语言级别的锁:
  • 在编程语言中,通过关键字实现锁机制。例如,Java中的synchronized关键字用于同步方法或代码块,确保在同一时刻只有一个线程可以访问被锁定的资源。
3. 互斥量(Mutex):
  • 在操作系统级别,可以使用互斥量确保同一时刻只有一个线程可以访问共享资源。

悲观锁的优点

  1. 数据一致性: 悲观锁确保了数据的一致性,因为在操作数据之前先获取了锁,避免了并发冲突。
  2. 简单直观: 实现相对简单,理解容易。

悲观锁的缺点

  1. 性能开销: 悲观锁的加锁操作会带来性能开销,尤其是在高并发的情况下,因为其他事务需要等待锁的释放。
  2. 死锁风险: 当多个事务相互等待对方释放锁时,可能发生死锁。
  3. 资源争用: 多个事务争用同一个资源时,可能导致大量的等待时间,降低系统的吞吐量。

使用悲观锁的示例:

在Java中,使用synchronized关键字可以实现悲观锁:

public class BankAccount {
    private double balance;
    // 同步方法,使用悲观锁
    public synchronized void deposit(double amount) {
        balance += amount;
    }
    // 同步代码块,使用悲观锁
    public void withdraw(double amount) {
        synchronized (this) {
            if (balance >= amount) {
                balance -= amount;
            } else {
                System.out.println("Insufficient funds");
            }
        }
    }
}

  synchronized关键字确保在同一时刻只有一个线程可以执行depositwithdraw方法。这就是一种悲观锁的实现方式。

相关文章
|
7月前
|
SQL 数据处理 数据库
乐观锁和悲观锁
乐观锁和悲观锁
67 0
|
3月前
|
SQL XML Java
乐观锁与悲观锁是什么?
本文详细分析了悲观锁和乐观锁的原理、区别、实现方式及应用场景。悲观锁假设冲突频繁,通过加锁保护数据一致性,适用于高并发冲突场景;乐观锁假设冲突较少,通过版本号或时间戳检测冲突,适用于读多写少场景。文章通过具体示例展示了两种锁机制的实现过程,并总结了其优缺点和适用场景,帮助读者根据实际需求选择合适的并发控制机制。
224 3
|
4月前
|
SQL 缓存 NoSQL
乐观锁的实现
乐观锁的实现
|
Java API 数据库
什么是乐观锁,什么是悲观锁?
在互联网公司面试中,很多小伙伴都被问到关于锁的理解。今天,我给小伙伴们来聊一聊我对锁的理解,不管我们互斥锁、自旋锁、重入锁、读写锁、行锁、表锁等等等等这些概念,我把他们都归纳为两种类型,乐观锁和悲观锁。
126 0
|
5月前
|
数据库
乐观锁介绍
乐观锁介绍
|
7月前
|
安全 Java 关系型数据库
乐观锁与悲观锁
【4月更文挑战第11天】乐观锁与悲观锁
49 3
|
7月前
|
关系型数据库 MySQL 数据处理
一文彻底理解乐观锁与悲观锁
一文彻底理解乐观锁与悲观锁
815 0
|
7月前
|
安全 关系型数据库 MySQL
悲观锁和乐观锁
悲观锁和乐观锁
|
NoSQL 算法 Java
乐观锁与悲观锁的实现
本文力求来通俗地讲讲编程中的乐观锁和悲观锁,以及分别是怎么实现的。
102 0
乐观锁与悲观锁的实现
|
负载均衡 Java
聊到悲观锁,是用什么来实现的呢?
优惠券服务是多实例部署形成的负载均衡集群
45 0