互斥锁(下):如何用一把锁保护多个资源

本文讨论了如何使用互斥锁有效地保护没有关联和有关联关系的多个资源,指出仅使用对象本身作为锁(如`this`)无法解决跨对象的并发问题。文章通过银行转账操作为例,揭示了在并发环境下,简单的同步方法可能导致数据不一致。提出了两种解决方案:所有对象持有同一唯一的对象作为锁或者使用类本身(如`Account.class`)作为共享锁。
摘要由CSDN通过智能技术生成

如何用一把锁保护多个资源

一、保护没有关联的多个资源

  • 举例:
class Account {
    锁:保护账户余额
    private final Object balLock = new Object();
    账户余额
    private Integer balance;
    锁:保护账户密码
    private final Object pwLock = new Object();
    账户密码
    private String password;
    取款
    void withdraw(Integer amt) {
        synchronized(balLock) {
            if (this.balance > amt){
                this.balance -= amt;
            }
        }
    }  
          查看余额
     Integer getBalance() {
        synchronized(balLock) {
            return balance;
        }
    }  
          更改密码
    void updatePassword(String pw){
        synchronized(pwLock) {
            this.password = pw;
        }
    }  
          查看密码
    String getPassword() {
        synchronized(pwLock) {
            return password;
        }
    }
}
  • 创建了两个final对象作为锁,各自管理各自的资源,很简单。
  • 也可以用一把互斥锁来保护多个资源。如上例:把所有的方法都增加同步关键字synchronized就可以了。但是用一个锁会导致性能太差,所有的操作都是串行的。
  • 用不同的锁对不同的资源进行精细化管理,能够提升性能。这种锁也叫做细粒度锁。

二、保护有关联关系的多个资源

  • 举例:银行业务里面的转账操作,账户 A 减少 100 元,账户 B 增加 100 元。这两个账户就是有关联关系的。那对于像转账这种有关联关系的操作,我们应该怎么去解决呢?
class Account {
    private int balance;
    // 转账
    void transfer(
        Account target, int amt){
        if (this.balance > amt) {
            this.balance -= amt;
            target.balance += amt;
        }
    }
}
  • 怎么保证转账操作 transfer() 没有并发问题呢?

  • 你可能会这样做:用户 synchronized 关键字修饰一下 transfer()方法就可以了,于是你很快就完成了相关的代码,如下所示:

class Account {
    private int balance;
    // 转账
    synchronized void transfer(Account target, int amt){
        if (this.balance > amt) {
            this.balance -= amt;
            target.balance += amt;
        }
    }
}
  • 在这段代码中,临界区内有两个资源,分别是转出账户的余额 this.balance 和转入账户的余target.balance,并且用的是一把锁 this,符合我们前面提到的,多个资源可以用一把锁来保护,这看上去完全正确呀。真的是这样吗?

  • 问题就出在 this 这把锁上,this 这把锁可以保护自己的余额 this.balance,却保护不了别人的余额 target.balance,就像你不能用自家的锁来保护别人家的资产,也不能用自己的票来保护别人的座位一样。

在这里插入图片描述

  • 假设线程 1 执行账户 A 转账户 B 的操作,线程 2 执行账户 B 转账户 C 的操作。这两个线程分别在两颗 CPU 上同时执行,那它们是互斥的吗?我们期望是,但实际上并不是。因为线程 1 锁定的是账户 A 的实例(A.this),而线程 2 锁定的是账户 B 的实例(B.this),所以这两个线程可以同时进入临界区 transfer()。同时进入临界区的结果是什么呢?线程 1 和线程 2 都会读到账户 B 的余额为 200,导致最终账户 B 的余额可能是300(线程 1 后于线程 2 写 B.balance,线程 2 写的 B.balance 值被线程 1 覆盖),可能是 100(线程 1 先于线程 2 写 B.balance,线程 1 写的 B.balance 值被线程 2 覆盖),就是不可能是 200 。

在这里插入图片描述

三、使用锁的正确姿势

  • 解决方案1:让所有对象都持有一个唯一性的对象。
class Account {
    private Object lock;
        private int balance;
        private Account();
        // 创建 Account 时传入同一个 lock 对象
        public Account(Object lock) {
            this.lock = lock;
        } 
        转账
        void transfer(Account target, int amt){
        // 此处检查所有对象共享的锁
        synchronized(lock) {
            if (this.balance > amt) {
                this.balance -= amt;
                target.balance += amt;
            }
        }
    }
}
  • 解决方案2:用Account.class 作为共享的锁
class Account {
    private int balance;
    // 转账
    void transfer(Account target, int amt){
        synchronized(Account.class) {
            if (this.balance > amt) {
                this.balance -= amt;
                target.balance += amt;
            }
        }
    }
}

.balance > amt) {
this.balance -= amt;
target.balance += amt;
}
}
}
}


![在这里插入图片描述](https://img-blog.csdnimg.cn/20210130195223663.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NjE1NjIwMA==,size_16,color_FFFFFF,t_70#pic_center)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值