前言
在上篇中的银行转账的例子中, 用Account.class作为互斥锁,虽然能保证并发问题,但是用户A、B、C、D,A转B,B转C是串行的,这是由于用锁Account.class将转账操作串行化了,性能就会很低,在现实生活中这两个转账操作是可以并行处理的,所以需要提升性能。
向现实世界要答案
例如古代,所有的记账都是在账本上操作的,A和B各自有两个账本,A转账给B,需要账员同时看A和B的账本都在不在,只有两个账本都在的话才能转账成功,否则就不能转账,如果只有一本账本,就不能记账,否则会出现一本记了而一本没记的情况。
将上面的转账场景放到编程中如何实现呢?其实用两把锁就可以实现了,两个账本各加一把锁。在transfer()方法内部,给this.balance加一个锁,给target也加一个锁,只有当两个锁都拿到之后才执行this.balance -= amt; target.balance += amt;
操作。
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;
}
}
}
}
}
没有免费的午餐
相对于Account.class作为互斥锁,上述的例子锁定的范围就很小了,这样的锁叫做细粒度锁
,使用细粒度锁可以提高并发度,是优化性能的一个重要手段。
使用细粒度的锁是有代价的,这个代价就是可能会导致死锁。
例如古代有A和B两个用户,A向B转帐,找记账员,单此时B向A也要转账(假设他们两个不认识,只知道用户唯一名),于是A和B都向柜员要账本,A拿到A的账本,B拿到B的账本,而此时A要等B归还账本,B要等A归还账本,这样就会无限制等下去,