互斥锁(下):如何用一把锁保护多个资源?

之前的文章里也说了,一把锁可以保护多个资源,所以受保护的资源和锁之间合理的关联关系应该是N:1的关系,上次我们直说了如何正确保护一个资源,但是没说如何正确保护多个资源,我们上次最后一个案例也说,两把锁保护两个资源,一个this,一个所属类,由于不互斥,所以会造成并发问题。

而产生问题,最主要的原因是,this对象和所属类存在必然关联的关系。

这也是今天要说的问题了,当我们要保护多个资源的时候,必然要分清楚,保护的资源们是否有关联。

保护没有关联的多个资源

比如说,球赛的门票和电影院的座位是没关系的,球赛的门票只能关联球赛的座位而不是电影院的座位。

然后同样应用到编程领域来说,例如从银行取款就会扣钱,更改账户密码就会改变,这就是两个没关系的资源。

代码如下,账户类Account有两个成员变量,分别是账户余额balance和账户密码password。取 款withdraw()和查看余额getBalance()操作会访问账户余额balance,我们创建一个final对象balLock作为锁 (类比球赛门票);而更改密码updatePassword()和查看密码getPassword()操作会修改账户密码 password,我们创建一个final对象pwLock作为锁(类比电影票)。不同的资源用不同的锁保护,各自管各自的,很简单。

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;				
		}		
	} 
}

当然,也可以用互斥锁来保护这样的多个没关系的资源,比如说,我们用this这一把锁,把账户类的资源(账户余额和用户密码)锁住,实现很简单,就是每个方法加一个sync就可以了。

但是有个缺点就是,性能差,而且,取款和改密码之类的应该是并行的,而不是串行,所以最好使用两把锁。

用不同的锁对受保护资源进行精细化管理,能够提升性能。这样的锁,叫做细粒度锁。

下面再来介绍一种复杂情况,保护有关联的资源

保护有关联的多个资源

之前说的问题是保护没有关联的多个资源,现在要说一种很复杂的情况,就是保护关联的资源。假如支付宝打款,A扣款100,B收款100.那么就是有关联关系的了。我们假设一个账户类:Account,该类有一个成员变量余额:balance,还有一个用于转账的方法:transfer(),然后怎么保证转账操作transfer()没有并发问题呢?

class Account {		
	private int balance;		
	//	转账		
	void transfer(Account target, int amt){				
			if	(this.balance > amt) {						
				this.balance -= amt;						
				target.balance += amt;				
			}		
	}	
}

相信你的直觉会告诉你这样的解决方案:用户synchronized关键字修饰一下transfer()方法就可以了,于是 你很快就完成了相关的代码,如下所示。

class Account {		
	private int balance;		
	//	转账		
	synchronized void transfer(Account target, int amt){				
			if	(this.balance > amt) {						
				this.balance -= amt;						
				target.balance += amt;				
			}		
	}	
}

但是这段代码中,临界区内有两个资源,分别是转出账户的余额this.balance和转入账户的余额target.balance,并且用的都是一把this锁,而问题也就出在这里。

this可以保护this.balance是没问题的,但是无法保护target.balance:

在这里插入图片描述

然后我们来具体分析一下,如果有ABC三个账户,余额都是200元,我们用两个线程分别执行两个转账操作:账户A转给账户B 100 账户B转给账户C 100,最后我们期望的结果是账户A的余额是100,账户B的余额200,账户C的余额是300.

我们假设一个线程1执行账户A转账户B的操作,一个线程2执行账户B转给账户C的操作。这两个线程在两个CPU上同时执行,所以不是互斥的。因为线程1锁定的是账户A的实例,也就是A.this,而线程2锁定的是账户B的实例,也就是B.this,所以这两个线程可以同时进入临界区transfer()。同时进入临界区的结果是什么呢?线程1和线程2都会读到账户B的余额是200,导致会出错误。

有可能线程1后于线程2写B.balance,线程2写的B.balance被线程1覆盖,也有可能是100,那就是相反过来执行,反正不可能是200.

在这里插入图片描述

使用锁的正确姿势

在上一篇文章中,我们用同一把锁保护多个资源,而实际上,只是我们的锁能够把资源覆盖起来,也就能保护了。在上面的例子中,this是对象级别的锁,所以A对象和B对象都有自己的锁,如何让A对象和B对象共享一把锁呢?

其实方案还是很多的,比如我们可以让所有对象都持有一把唯一性的对象,这个对象在创建Account的时候传入,方案有了,完成代码就简单了。

示例代码如下,我们把Account默认构造函数变为 private,同时增加一个带Object lock参数的构造函数,创建Account对象时,传入相同的lock,这样所有的 Account对象都会共享这个lock了。

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的实例的时候,传入的不是同一个对象,就会出现一定高度问题,而在真正的项目中,又很少会传入同一个对象进去,所以还有其他的方案。

那就是用Account.class作为共享锁 ,因为Account.class是所有Account对象共享的,而这个对象也是jvm在加载Account的时候创建的,所以我们不用担心它的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;						
			}				
		}		
	}	
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值