互斥锁 — 如何一把锁保护多个资源

>> 保护没有关联关系的多个资源

细粒度锁:用不同的锁对受保护资源进行进行精细化管理,能够提升性能。

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;
    }
  }
}

上面的代码中我们可以使用this 这一把锁来管理账户类的所有资源,但是性能太差,对导致上面所有的操作都是串行的。

>>保护有关联关系的多个资源

例子: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;
    }
  } 
}
两个临界资源:转出账户余额this.balance 和转入账户余额target.balance。
代码问题出在this 这把锁可以保护自己的余额但却不能保护别人的余额。
this.balance & target.balance

例子:假设有 A、B、C 三个账户,余额都是 200 元。线程 1 执行账户 A 转账户 B 的操作,线程 2 执行账户 B 转账户 C 的操作。


转账示意图

线程 1 和线程 2 都会读到账户 B 的余额为 200,导致最终账户 B 的余额可能是 300(线程 1 后于线程 2 写 B.balance,线程 2 写的 B.balance 值被线程 1 覆盖),可能是 100(线程 1 先于线程 2 写 B.balance,线程 1 写的 B.balance 值被线程 2 覆盖),就是不可能是 200。

>> 使用锁的正确姿势

上面的例子中,this 是对象级别的锁,所以 A 对象和 B 对象都有自己的锁。
修改:

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;
      }
    }
  }
}
有个小瑕疵,创建 Account 对象的代码很可能分散在多个工程中,传入共享的 lock 真很难。

再修改:使用 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;
      }
    }
  } 
}
不过这种方式在实践中是不可行的,所有的转账操作都变串行化了。
使用共享的锁 Account.class

“原子性”的本质:操作的中间状态对外不可见。

你可能感兴趣的:(互斥锁 — 如何一把锁保护多个资源)