4. 互斥锁(下):如何用一把锁保护多个资源 - 理论基础

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

各自创建自己锁保护即可,如下:

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

我们也可以用当前对象作为互斥锁,但是导致取款,查看密码等操作全部编程串行的,影响性能。可以用细粒度锁。

**细粒度锁:**用不同的锁对资源进行精细化管理,有利于提高性能。

2. 保护有关联关系的资源

例如账户A减少100元,账户B增加100元,一个成员变量balance,一个转账方法transfer().
原始代码:

class Account {
  private int balance;
  // 转账
  void transfer(Account target, int amt){
    if (this.balance > amt) {
      this.balance -= amt;
      target.balance += amt;
    }
  } 
}

直觉想用synchronized 字段修饰方法,如下:

class Account {
	// 账户余额
	private int balance;
	// 转账
	synchronized void transfer(Account target, int amt) {
		if (this.balance > amt) {
			this.balance -= amt;
			target.balance += amt;
		}
	}
}

该段代码的this锁可以保护自己的余额this.balance,但保护不了别人的target.balance.

在这里插入图片描述

问题描述:A、B和C账户各有200元,A给B转100元,B给C转100元,希望的结果是A为100元,B为200元,C为300元。假设线程1处理A给B转100元,线程2处理B给C转100元,分别在两个CPU上运行。线程1锁定this.A实例,线程2锁定this.B实例,两个线程同时进入临界区,都同时读到B为200元,那么最终B有可能是300元(线程1的操作结果覆盖线程2的操作)或者是100元(线程2的操作结果覆盖线程2的操作)。

本质:同个资源用了不同的锁。这个例子中:同个资源是B的账户,不同的锁是A和B两个实例的锁。

在这里插入图片描述

3. 正确使用锁保护多个资源

多个资源用共享锁保护即可。可以用唯一对象当做共享锁。如下代码:

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.class解决。这个对象是 Java 虚拟机在加载 Account 类的时候创建的,可以确保它的唯一性。

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由JVM加载,并且确保唯一性。

4.总结

原子性问题:本质是多个资源有一致性要求,操作的中间状态对外不可见。

5.课后思考

在第一个示例程序里,我们用了两把不同的锁来分别保护账户余额、账户密码,创建锁的时候,我们用的是:private final Object xxxLock = new Object();

如果账户余额用 this.balance 作为互斥锁,账户密码用 this.password 作为互斥锁,你觉得是否可以呢?

答案:不可用可变对象做锁。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值