5、习题-转账问题
package com.alibaba.threadSafe;
import lombok.extern.slf4j.Slf4j;
import java.util.Random;
@Slf4j
public class ExerciseTransfer {
public static void main(String[] args) throws InterruptedException {
Account a = new Account(1000);
Account b = new Account(1000);
Thread t1 = new Thread(()->{
for (int i = 0; i < 1000; i++) {
a.transfer(b,randomAmount());// a 给 b 转钱
}
},"t1");
Thread t2 = new Thread(()->{
for (int i = 0; i < 1000; i++) {
b.transfer(a,randomAmount());// b 给 a 转钱
}
},"t2");
t1.start();
t2.start();
t1.join();
t2.join();
// 查看转账 2000 次后的总余额
log.debug("total:{}",(a.getMoney() + b.getMoney()));
}
// Random 为线程安全
static Random random = new Random();
// 随机 1~100
public static int randomAmount() {
return random.nextInt(100) +1;
}
}
class Account {
private int money;
public Account(int money) {
this.money = money;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
// 转账
public void transfer(Account target,int amount) {
if (this.money > amount) {
this.setMoney(this.getMoney() - amount);
target.setMoney(target.getMoney() + amount);
}
}
}
运行结果
分析
transfer()
涉及到共享变量的读写,所以以下代码属于临界区
if (this.money > amount) {
this.setMoney(this.getMoney() - amount);
target.setMoney(target.getMoney() + amount);
}
那这里谁是共享变量呢?显然,a如果要转账,那么 a的余额 this.money
是共享变量,而 b 的余额也会修改,所以 b的余额 target.getMoney()
也是共享的。所以现在问题来了,现在有 多个需要保护的共享变量,那能不能在 transfer()
方法上加 synchronized
呢?其实不行,因为 synchronized
加在成员方法上,其实等价于加在 this
对象上,但 this
对象保护的是哪个共享变量呢?保护的是 this.money
,没办法影响到 另一个对象,所以 synchronized
要锁住的对象也得是 this.money()
和 target.getMoney()
共享的,而 this 和 target
又共享了 Account
类,Account
类对其所有对象都是共享的,所以可以用以下方法解决。其实这里锁的是this的话,加了和没加一样,因为锁的是两个不同的对象,必须锁住两个线程所唯一的对象
synchronized(Account.class) {
if (this.money > amount) {
this.setMoney(this.getMoney() - amount);
target.setMoney(target.getMoney() + amount);
}
}
可以想成他们对应一个转账关系。将这种关系在封装一个类,类的成员变量为参与者,类方法实现相互转账的操作(分支 transfer()
实现,且 transfer()
也有对象锁),然后个这个方法加一个对象锁就完事了。这里分开锁的话就会造成死锁互相等待,统一类锁就不会,因为统一了整个线程类。如果分开当线程切换过快就会产生互相等待