Java并发4:互斥锁(下)
受保护资源和锁之间合理的关联关系应该是 N:1 的关系,即可以用一把锁来保护多个资源,但不能多把锁保护一个资源。
要保护多个资源,首先要区分这些资源是否存在关联关系。
保护没有关联关系的多个资源
有一用户账户类Account。
对于用户账户,有账号,密码,余额等属性。
密码和余额是没有关联关系的,这样就可以建两把锁,来保护密码和余额。不同的资源用不同的锁来保护。
当然也可以用一把锁来保护,比如直接用用户对象this保护.
但这样问题就是性能太差,对密码和余额操作都需要共享一把锁。
所以还是对不同的资源采取不同的锁进行精细化管理,能够提升性能,即细粒度锁。
保护有关联关系的多个资源
比如A账户向B账户转账,这样要同时锁定A&B两个资源。
保证锁能覆盖所有的保护资源。
- 传入同一个锁进行转账
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
对象时,传入的 lock
不是同一个对象,就会出现问题。
在真实的项目场景中,创建 Account
对象的代码很可能分散在多个工程中,传入共享的 lock
实际上很难。
- 用Account.class作为共享锁
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
中的所有操作都会用同一个锁。
思考
用两把不同的锁来分别保护账户余额、账户密码,创建锁的时候:
如果账户余额用``` this.balance``` 作为互斥锁,账户密码用``` this.password ```作为互斥锁,是否可以?
不可以。不能用可变对象做锁。
```balance```是```Integer```,```password```是```String```,都是不可变对象,一但对他们进行赋值就会变成新的对象,加的锁就失效了。
核心问题有两点:
+ 一个是锁有可能会变化:如果锁发生变化,就意味着失去了互斥功能。
+ 一个是 ```Integer``` 和 ```String``` 类型的对象不适合做锁:```Boolean```、 ```Integer``` 和 ```String``` 类型的对象在 JVM 里可能被重用。重用意味着锁可能被其他代码使用,如果其他代码 ```synchronized(锁)```,且不释放,那程序就永远拿不到锁,这是隐藏的风险。
**锁应是私有的、不可变的、不可重用的。**
### 别人的总结
>是否可以在```Account```中添加一个静态```object```,通过锁这个```object```来实现一个锁保护多个资源,如下:
>
>```java
>class Account {
> private final 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;
> }
> }
> }
>}
——yuc
note:这样的话所有用户转账操作用的是同一把锁。