如何用一把锁保护多个资源
一、保护没有关联的多个资源
- 举例:
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)