1)当我们要保护多个资源时,首先要做的是什么?
-
分析这些资源是否存在关联关系
2)应该怎样保护没有关联关系的多个资源?
-
应该怎样保护没有关联关系的多个资源?**就是没有关联关系的,这种场景非常容易解决,那就是球赛有球赛的门票,电影院有电影院的门票,各自管理各自的
-
对应到编程领域,例如,银行业务中有针对账户余额(余额是一种资源)的取款操作,也有针对账户密码(密码也是一种资源)的更改操作,我们可以为账户余额和账户密码分配不同的锁来解决并发问题
3)保护没有关联关系的多个资源实例?
-
管密码的是一把锁,管余额的是另外一把锁,各管各的,井水不犯河水。
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;
}
}
}
3.1)上面的代码中我们可以用一把锁来保护密码和余额这两个资源吗?
-
可以,在所有方法上加synchronized关键字,锁都是this。
3.2)用一把锁管密码和余额会存在什么问题?
-
性能太差,会导致取款、查看余额、修改密码、查看密码这四个操作都是串行的
3.3)那使用两把锁的优点是什么?
-
用两把锁,取款和修改密码是可以并行的。
-
不同的锁对受保护资源进行精细化管理,能够提升性能
3.4)这种进行精细化管理的锁叫什么名字?
-
细粒度锁
4)应该怎样保护有关联关系的多个资源?
-
银行业务里面的转账操作,账户 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;
}
}
}
4.1)怎么保证转账操作 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锁,这看上去完全正确的啊,有什么问题?
4.2)上面代码中的问题出现在哪里?
-
问题就出在 this 这把锁上。this 这把锁可以保护自己的余额 this.balance,却保护不了别人的余额 target.balance。
-
你不能用自家的锁来保护别人家的资产,也不能用自己的票来保护别人的座位一样。
4.3)上面tihs锁模型图是怎样的?
4.4)this只能保护自己,不能保护他人的实例?
A、B、C 三个账户,余额都是 200 元。
两个线程分别执行两个转账操作
-
A 转给账户 B 100 元
-
B 转给账户 C 100 元
期望的结果A 是 100 元, B 是 200 元, C 是 300 元。
假设线程 1 执行账户 A 转账户 B 的操作,线程 2 执行账户 B 转账户 C 的操作。
4.4.1)这两个线程分别在两颗 CPU 上同时执行,那它们是互斥的吗?
-
不是。线程 1 锁定的是账户 A 的实例(A.this),线程 2 锁定的是账户 B 的实例(B.this)。锁的对象不一样,他们两可以同时进入临界区,然后把B=200的值都读到自己的线程资源里面。
4.4.2)当两个线程执行完成后,B可能的值有哪些?
-
100 :线程1先执行,线程2后执行然后覆盖掉线程1的B值。
-
300:线程2先执行,线程1后执行然后覆盖掉线程1的B值。
-
反正就是不可能是正确的200!
4.5)那么该怎样正确的去使用一把锁避免两个关联资源没有保护周全的情况?
-
用一把范围大的锁
在上面的例子中,this 是对象级别的锁,所以 A 对象和 B 对象都有自己的锁。
4.6)如何让 A 对象和 B 对象共享一把锁呢?
-
让所有对象都持有一个唯一性的对象,这个对象在创建 Account 时传入。
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 对象的时候必须传入同一个对象,如果不是同一个那就完蛋了。
-
用 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;
}
}
}
}
5)在第一个示例程序里,我们用了两把不同的锁来分别保护账户余额、账户密码,创建锁的时候,我们用的是:private final Object xxxLock = new Object();,如果账户余额用 this.balance 作为互斥锁,账户密码用 this.password 作为互斥锁,你觉得是否可以呢?
-
不能用balance和password做为锁对象。这两个对象balance是Integer,password是String都是可变对象,一但对他们进行赋值就会变成新的对象,加的锁就失效了。