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 作为互斥锁,你觉得是否可以呢?
答案:不可用可变对象做锁。