乐观锁和悲观锁(结合实例)

🌺乐观锁和悲观锁

乐观锁和悲观锁是在并发编程中用于解决资源竞争问题的两种不同策略。

乐观锁

  • 假设多个线程访问共享资源时,认为它们之间的冲突很少发生。
  • 乐观锁不会对共享资源进行加锁,而是通过使用版本号或时间戳等机制来检测是否发生了冲突。
  • 当一个线程要修改共享资源时,它先读取该资源的当前版本信息,然后进行计算和修改。在提交结果之前,线程会再次检查资源的版本信息是否有变化,如果没有变化则提交成功,否则需要根据具体情况处理冲突。
  • 典型的乐观锁实现包括使用CAS(Compare and Swap)操作、版本号或时间戳等方式。

悲观锁

  • 假设多个线程访问共享资源时,认为它们之间的冲突很常见。
  • 悲观锁采用较保守的方式,即在访问共享资源之前先获取锁,确保其他线程无法同时访问资源。
  • 当一个线程获得了悲观锁后,其他线程必须等待该线程释放锁才能继续访问资源。
  • 典型的悲观锁实现包括使用互斥量(Mutex)、读写锁(ReadWriteLock)等方式。

🐛乐观锁实例

考虑一个简单的账户余额更新场景,多个线程同时对同一个账户进行存款操作。使用乐观锁的方式可以如下实现:

  1. 定义一个账户对象,包含账户ID和账户余额字段。
  2. 账户对象中添加一个版本号字段。
  3. 当一个线程要更新账户余额时,先读取账户对象的当前版本号和余额。
  4. 然后根据需求计算新的余额并更新账户对象的版本号。
  5. 最后检查账户对象的版本号是否与之前读取的版本号相同,如果相同则提交更新,否则需要处理冲突。
class Account {
    private int id;
    private int balance;
    private int version;

    public Account(int id, int balance) {
        this.id = id;
        this.balance = balance;
        this.version = 0;
    }

    public synchronized void deposit(int amount) {
        int currentVersion = this.version;
        int currentBalance = this.balance;

        // 计算新的余额
        int newBalance = currentBalance + amount;

        // 更新版本号
        this.version++;

        // 检查版本号是否一致
        if (currentVersion == this.version - 1) {
            this.balance = newBalance;
            System.out.println("Deposit successful. New balance: " + this.balance);
        } else {
            System.out.println("Deposit failed due to conflict.");
        }
    }
}

在上述示例中,每个线程会读取账户对象的版本号和余额,并在更新之前检查版本号是否发生了变化。如果版本号一致,说明没有冲突,可以更新账户余额;否则,需要处理冲突。在我们的实际项目中乐观一般为数据库层面的 为更新语句 update tableName set amount=#{amount} where userId =#{userId } and version=#{version} 如果更新条数为0则是更新失败

🐛悲观锁实例

假设有一个银行账户管理系统,多个用户同时想要转账到同一个目标账户上。使用悲观锁可以避免并发修改账户余额的问题。

  1. 在目标账户对象中添加一个锁字段,表示当前该账户是否被锁定。
  2. 当一个用户要进行转账操作时,先查询目标账户的锁状态,如果已经被其他用户锁定,则等待。
  3. 如果没有被锁定,则将目标账户锁定并进行转账操作。
  4. 转账完成后释放锁。
class BankAccount {
    private int id;
    private int balance;
    private boolean locked;

    public BankAccount(int id, int balance) {
        this.id = id;
        this.balance = balance;
        this.locked = false;
    }

    public void transferTo(BankAccount targetAccount, int amount) {
        // 检查目标账户是否被锁定
        while (targetAccount.isLocked()) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        // 锁定目标账户
        targetAccount.lock();

        // 转账操作
        this.balance -= amount;
        targetAccount.setBalance(targetAccount.getBalance() + amount);
        System.out.println("Transfer successful.");

        // 释放锁定
        targetAccount.unlock();
    }

    public synchronized boolean isLocked() {
        return locked;
    }

    public synchronized void lock() {
        this.locked = true;
    }

    public synchronized void unlock() {
        this.locked = false;
    }

    public synchronized int getBalance() {
        return balance;
    }

    public synchronized void setBalance(int balance) {
        this.balance = balance;
    }
}

在上述示例中,当一个用户要进行转账操作时,它会检查目标账户的锁状态。如果已经被其他用户锁定,则当前用户等待;否则,将目标账户锁定并进行转账操作。转账完成之

🐛避免死锁的方法

要避免死锁的发生,可以采取以下几种方法:

  1. 避免循环等待:确保线程获取锁的顺序是一致的,避免出现循环依赖的情况。例如,可以按照固定的顺序获取多个锁,或者使用资源分级的方式来避免循环等待。

  2. 使用超时机制:在尝试获取锁时设置一个超时时间,如果超过指定的时间还未获取到锁,则放弃获取并进行其他处理。这样可以避免因为某个线程无法获取锁而导致整个系统出现死锁。

  3. 引入死锁检测和恢复机制:通过监控系统中的锁请求和持有关系,可以实时检测是否存在潜在的死锁情况,并采取相应的措施解除死锁。常见的策略包括资源剥夺、撤销进程、回滚操作等。

  4. 合理设置锁的粒度:将锁的粒度尽量缩小,只在必要的代码块上加锁,避免长时间持有大范围的锁。这样可以减少不必要的等待和竞争,降低死锁发生的概率。

  5. 使用并发工具类:在Java中,可以使用java.util.concurrent包下的并发工具类,如ReentrantLockSemaphore,这些工具类已经内部实现了一些避免死锁的机制。

  6. 良好的代码设计:合理规划线程之间的依赖关系,避免多个线程之间相互等待对方的资源释放。设计时要考虑到并发访问和资源竞争的可能性,尽量避免出现临界区问题。

综上所述,避免死锁需要注意锁的获取顺序、使用超时机制、引入死锁检测和恢复机制、合理设置锁的粒度、使用并发工具类以及良好的代码设计。这些方法可以帮助我们减少死锁的概率和影响。

🌺总结

乐观锁和悲观锁各有优缺点:

  • 乐观锁适用于读多写少、冲突发生较少的情况,避免了不必要的加锁开销。但是在冲突频繁的情况下可能需要多次重试。
  • 悲观锁适用于写多读少、冲突发生较多的情况,可以通过加锁保证数据一致性。但是过多的加锁会导致线程阻塞和性能降低。

选择使用乐观锁还是悲观锁取决于实际的业务场景和需求,并且在实现时需要考虑并发性能和数据一致性的权衡。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值