上篇文章中提到受保護(hù)的資源和鎖之間合理的關(guān)系應(yīng)該是 N:1 的關(guān)系,也就是說用一把鎖來保護(hù)多個(gè)資源伐厌。
分兩種情況:
1.保護(hù)沒有關(guān)聯(lián)關(guān)系的多個(gè)資源
對比現(xiàn)實(shí)生活承绸,球場的座位和電影院的座位沒有關(guān)聯(lián)關(guān)系,這種場景非常容易解決挣轨,就是球場有球場的門票军熏,電影院有電影院的門票,各管各的卷扮。
對應(yīng)到編程領(lǐng)域荡澎,兩個(gè)沒有關(guān)聯(lián)關(guān)系的對象用不同的鎖來解決并發(fā)問題。
下面一段代碼中晤锹,賬戶密碼和余額沒有關(guān)聯(lián)關(guān)系摩幔。
class Account {
// 鎖:保護(hù)賬戶余額
private final Object balLock
= new Object();
// 賬戶余額
private Integer balance;
// 鎖:保護(hù)賬戶密碼
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;
}
}
}
當(dāng)然我們也可以用一把鎖來保護(hù),實(shí)現(xiàn)方法是所有的方法用synchronized修飾鞭铆,鎖為this或衡。但是性能太差。用不同的鎖來對資源進(jìn)行精細(xì)化管理车遂,能提升性能封断。這種鎖,叫細(xì)粒度鎖艰额。
2.保護(hù)有關(guān)聯(lián)關(guān)系的多個(gè)資源
例如兩個(gè)賬戶A和B澄港,A轉(zhuǎn)給B100元,A余額減少100柄沮,B余額增加一百回梧。這倆賬戶是有關(guān)聯(lián)關(guān)系的。以下為賬戶類代碼:
class Account {
private int balance;
// 轉(zhuǎn)賬
void transfer(
Account target, int amt){
if (this.balance > amt) {
this.balance -= amt;
target.balance += amt;
}
}
}
那么如何解決并發(fā)祖搓,是下面這樣嗎狱意?
class Account {
private int balance;
// 轉(zhuǎn)賬
synchronized void transfer(
Account target, int amt){
if (this.balance > amt) {
this.balance -= amt;
target.balance += amt;
}
}
}
看似沒有問題,但如果你真的覺得沒有問題拯欧,說明還沒有理解鎖和資源的對應(yīng)關(guān)系详囤。synchronized關(guān)鍵字在這里鎖的是this對象,卻無法鎖住target賬戶對象。this這把鎖能保護(hù)自己的余額balance藏姐,卻保護(hù)不了別人的余額隆箩。看圖:
假設(shè)還有一個(gè)賬戶C羔杨,初始每個(gè)賬戶均有200捌臊,現(xiàn)在線程1執(zhí)行A轉(zhuǎn)賬給B,線程2執(zhí)行B轉(zhuǎn)賬給C兜材,這倆線程分別同時(shí)在兩個(gè)CPU上執(zhí)行理澎,他們是互斥的嗎?我們期望是曙寡,但實(shí)際上不是糠爬。兩個(gè)線程分別同時(shí)鎖定A的實(shí)例和B的實(shí)例,同時(shí)進(jìn)入臨界區(qū)举庶,同時(shí)讀到B的余額為200执隧,所以最終結(jié)果是B可能是為100,也可能為300灯变,就是不可能為200殴玛。
使用鎖的正確姿勢
那么如何用一把鎖保護(hù)多個(gè)資源呢,對比現(xiàn)實(shí)世界的“包場”添祸,只要我們的鎖能覆蓋所有受保護(hù)的資源就可以了。這里介紹兩種方式:
1.不同對象共享同一把鎖寻仗,所有對象持有一個(gè)唯一的對象就可以了刃泌。
class Account {
private Object lock;
private int balance;
//構(gòu)造函數(shù)私有
private Account(){}
// 創(chuàng)建 Account 時(shí)傳入同一個(gè) lock 對象
public Account(Object lock) {
this.lock = lock;
}
// 轉(zhuǎn)賬
void transfer(Account target, int amt){
// 此處檢查所有對象共享的鎖
synchronized(lock) {
if (this.balance > amt) {
this.balance -= amt;
target.balance += amt;
}
}
}
}
這的確能解決問題署尤,但是有個(gè)瑕疵耙替,他要求創(chuàng)建Account對象時(shí)候必須傳入同一個(gè)對象,如果傳入的不是同一個(gè)lock曹体,那就gg了俗扇。而且現(xiàn)實(shí)項(xiàng)目中,有時(shí)傳入共享的lock真的很難箕别,因此它缺乏的實(shí)踐的可行性铜幽。
2.使用XXX.class
這里Account.class是所有Account對象共享的,根據(jù)雙親委派原則可以保證它的唯一性串稀。
class Account {
private int balance;
// 轉(zhuǎn)賬
void transfer(Account target, int amt){
synchronized(Account.class) {
if (this.balance > amt) {
this.balance -= amt;
target.balance += amt;
}
}
}
}
這個(gè)算是更好的方法除抛,但是有個(gè)現(xiàn)實(shí)的問題你有注意到嗎?
所有的轉(zhuǎn)賬操作都變成串行的了母截。還能同時(shí)轉(zhuǎn)賬嗎到忽?