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

总结

原子性”的本质是什么?其实不是不可分割,不可分割只是外在表现,其本质是多个资源间有一致性的要求,操作的中间状态对外不可见。例如,在 32 位的机器上写 long 型变量有中间状态(只写了 64 位中的 32 位),在银行转账的操作中也有中间状态(账户 A 减少了 100,账户 B 还没来得及发生变化)。
所以解决原子性问题,是要保证中间状态对外不可见。

课后思考

在第一个示例程序里,我们用了两把不同的锁来分别保护账户余额、账户密码,创建锁的时候,我们用的是:private final Object xxxLock = new Object();,如果账户余额用 this.balance 作为互斥锁,账户密码用 this.password 作为互斥锁,你觉得是否可以呢?

精彩回答

不可以。因为balance为integer对象,当值被修改相当于换锁,还有integer有缓存-128到127,相当于同一个对象。
用this.balance 和this.password 都不行。在同一个账户多线程访问时候,A线程取款进行this.balance-=amt;时候此时this.balance对应的值已经发生变换,线程B再次取款时拿到的balance对应的值并不是A线程中的,也就是说不能把可变的对象当成一把锁。this.password 虽然说是String修饰但也会改变,所以也不行。老师所讲的例子中的两个Object无论多次访问过程中都未发生变化,不能用可变对象做锁
//是否可以在Account中添加一个静态object,通过锁这个object来实现一个锁保护多个资源,如下:
class Account {
  private static Object lock = new Object();
  private int balance;
  // 转账
  void transfer(Account target, int amt){
    synchronized(lock) {
      if (this.balance > amt) {
        this.balance -= amt;
        target.balance += amt;
      }
    }
  } 
}
这种方式比锁class更安全,因为这个缺是私有的。有些最佳实践要求必须这样做。

标签: none

评论已关闭