并发编程之互斥锁
经典场景之转账
声明账户类:Account,该类有一个成员变量余额:balance,还有一个用于转账的方法:transfer()。
class Account {
//余额
private int balance;
// 给目标账户转账
void transfer(Account target, int amt){
if (this.balance > amt) {
//自己余额做减法
this.balance -= amt;
//目标账户余额做加法
target.balance += amt;
}
}
}
假设有三个账户:账户A、账户B、账户C,三个账户里面的余额都是200。同一时刻,账户A 向 账户B 转账100,账户B 向 账户C 转账100,正确结果应该是 账户A 余额为100,账户B 余额为200, 账户C 余额为 300。然后怎么保证转账操作 transfer() 没有并发问题呢 ?
方法1,用 synchronized 关键字修饰一下 transfer() 方法,如下:
class Account {
private int balance;
synchronized void transfer(
Account target, int amt){
if (this.balance > amt) {
this.balance -= amt;
target.balance += amt;
}
}
}
分析:synchronized放在普通方法上用的锁是:this。
线程1(A给B转账):this 对象是 账户A;
线程2(B给C转账):this 对象是 账户B;
线程1 与 线程2 并不互斥,因为要拿的是不同的锁,所以可以发生在同一时刻。
结果:
账户 B 的余额可能是 300(线程 1 后于线程 2 写 B.balance,线程 2 写的 B.balance 值被线程 1 覆盖),也可能是 100(线程 1 先于线程 2 写 B.balance,线程 1 写的 B.balance 值被线程 2 覆盖),就是不可能是 200。
总结: 该方法 不能 保证转账操作 transfer()是并发安全的。
方法2
class Account {
private int balance;
// 转账
void transfer(Account target, int amt){
synchronized(Account.class) {
if (this.balance > amt) {
this.balance -= amt;
target.balance += amt;
}
}
}
}
分析:synchronized锁的是全局唯一的:Account.class。
线程1(A给B转账):锁对象是 Account.class;
线程2(B给C转账):锁对象是 Account.class;
线程1 与 线程2 互斥,因为要拿的是相同的锁,所以不可以发生在同一时刻。
总结: 该方法 可以 保证转账操作 transfer()是并发安全的。但是,所有的账户转账操作都使用的同一个锁:Account.class。那么所有的转账操作就会变成串行的操作,所以该方法虽然安全,可是确实不可行的。
方法3
class Account {
private int balance;
// 转账
void transfer(Account target, int amt){
// 锁定转出账户
synchronized(this) {
// 锁定转入账户
synchronized(target) {
if (this.balance > amt) {
this.balance -= amt;
target.balance += amt;
}
}
}
}
}
分析:方法1是因为只锁住了 this 对象,线程1 和 线程2 要锁的对象不互斥导致线程不安全;方法2是因为用一个全局的锁,导致所有账户的转账操作变成了串行。所以方法3,使用两把锁,锁住当前账户对象 和 目标对象。
线程1(A给B转账):1)锁住 账户A,2)锁住 账户B;
线程2(B给C转账):1)锁住 账户B,2)锁住 账户A;
线程1 与 线程2 互斥,因为不可能都同时获取到账户A和账户B的锁,所以不可以发生在同一时刻。
总结:该方法可以保证并发安全。但是,如果当 线程1 获取到 账户A 的锁(等待线程2释放 账户B 的锁),线程2 获取到 账户B 的锁(等待线程2释放 账户B 的锁)。两个线程就陷入了死锁状态。
所以,未完待续。。。