JAVA并发实践-04 | 互斥锁(下):如何用一把锁保护多个资源?
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;
}
}
}
总结
原子性”的本质是什么?其实不是不可分割,不可分割只是外在表现,其本质是多个资源间有一致性的要求,操作的中间状态对外不可见。例如,在 32 位的机器上写 long 型变量有中间状态(只写了 64 位中的 32 位),在银行转账的操作中也有中间状态(账户 A 减少了 100,账户 B 还没来得及发生变化)。
所以解决原子性问题,是要保证中间状态对外不可见。
课后思考
在第一个示例程序里,我们用了两把不同的锁来分别保护账户余额、账户密码,创建锁的时候,我们用的是:private final Object xxxLock = new Object();,如果账户余额用 this.balance 作为互斥锁,账户密码用 this.password 作为互斥锁,你觉得是否可以呢?
精彩回答
不可以。因为balance为integer对象,当值被修改相当于换锁,还有integer有缓存-128到127,相当于同一个对象。
用this.balance 和this.password 都不行。在同一个账户多线程访问时候,A线程取款进行this.balance-=amt;时候此时this.balance对应的值已经发生变换,线程B再次取款时拿到的balance对应的值并不是A线程中的,也就是说不能把可变的对象当成一把锁。this.password 虽然说是String修饰但也会改变,所以也不行。老师所讲的例子中的两个Object无论多次访问过程中都未发生变化,不能用可变对象做锁
//是否可以在Account中添加一个静态object,通过锁这个object来实现一个锁保护多个资源,如下:
class Account {
private static Object lock = new Object();
private int balance;
// 转账
void transfer(Account target, int amt){
synchronized(lock) {
if (this.balance > amt) {
this.balance -= amt;
target.balance += amt;
}
}
}
}
这种方式比锁class更安全,因为这个缺是私有的。有些最佳实践要求必须这样做。
评论已关闭