并發(fā)編程領域豌研,有兩大核心問題:
一個是互斥妹田,即同一時刻只允許一個線程訪問共享資源;
另一個是同步鹃共,即線程之間如何通信鬼佣、協(xié)作。這兩大問題霜浴,管程都是能夠解決的沮趣。
Java SDK并發(fā)包通過Lock和Condition兩個接口來實現(xiàn)管程,
其中Lock用于解決互斥問題坷随,
Condition用于解決同步問題。
Lock 使用
synchronized申請資源的時候驻龟,如果申請不到温眉,線程直接進入阻塞狀態(tài)了,而線程進入阻塞狀態(tài)翁狐,啥都干不了类溢,也釋放不了線程已經(jīng)占有的資源。但我們希望的是:
對于“不可搶占”這個條件露懒,占用部分資源的線程進一步申請其他資源時闯冷,如果申請不到,可以主動釋放它占有的資源懈词,這樣不可搶占這個條件就破壞掉了蛇耀。
如果我們重新設計一把互斥鎖去解決這個問題,那該怎么設計呢坎弯?我覺得有三種方案纺涤。
1.能夠響應中斷。synchronized的問題是抠忘,持有鎖A后撩炊,如果嘗試獲取鎖B失敗,那么線程就進入阻塞狀態(tài)崎脉,一旦發(fā)生死鎖拧咳,就沒有任何機會來喚醒阻塞的線程。但如果阻塞狀態(tài)的線程能夠響應中斷信號囚灼,也就是說當我們給阻塞的線程發(fā)送中斷信號的時候骆膝,能夠喚醒它祭衩,那它就有機會釋放曾經(jīng)持有的鎖A。這樣就破壞了不可搶占條件了谭网。
2.支持超時汪厨。如果線程在一段時間之內沒有獲取到鎖,不是進入阻塞狀態(tài)愉择,而是返回一個錯誤劫乱,那這個線程也有機會釋放曾經(jīng)持有的鎖。這樣也能破壞不可搶占條件锥涕。
3.非阻塞地獲取鎖衷戈。如果嘗試獲取鎖失敗,并不進入阻塞狀態(tài)层坠,而是直接返回殖妇,那這個線程也有機會釋放曾經(jīng)持有的鎖。這樣也能破壞不可搶占條件破花。
創(chuàng)建的鎖的具體類名是ReentrantLock谦趣,這個翻譯過來叫可重入鎖,這個概念前面我們一直沒有介紹過座每。所謂可重入鎖前鹅,顧名思義,指的是線程可以重復獲取同一把鎖峭梳。例如下面代碼中舰绘,當線程T1執(zhí)行到 ① 處時,已經(jīng)獲取到了鎖 rtl 葱椭,當在 ① 處調用 get()方法時捂寿,會在 ② 再次對鎖 rtl 執(zhí)行加鎖操作。此時孵运,如果鎖 rtl 是可重入的秦陋,那么線程T1可以再次加鎖成功;如果鎖 rtl 是不可重入的掐松,那么線程T1此時會被阻塞踱侣。
除了可重入鎖,可能你還聽說過可重入函數(shù)大磺,可重入函數(shù)怎么理解呢抡句?指的是線程可以重復調用?顯然不是杠愧,所謂可重入函數(shù)待榔,指的是多個線程可以同時調用該函數(shù),每個線程都能得到正確結果;同時在一個線程內支持線程切換锐锣,無論被切換多少次腌闯,結果都是正確的。多線程可以同時執(zhí)行雕憔,還支持線程切換姿骏,這意味著什么呢?線程安全啊斤彼。所以分瘦,可重入函數(shù)是線程安全的。
在使用ReentrantLock的時候琉苇,你會發(fā)現(xiàn)ReentrantLock這個類有兩個構造函數(shù)嘲玫,一個是無參構造函數(shù),一個是傳入fair參數(shù)的構造函數(shù)并扇。fair參數(shù)代表的是鎖的公平策略去团,如果傳入true就表示需要構造一個公平鎖,反之則表示要構造一個非公平鎖穷蛹。
鎖的最好的實際
永遠只在更新對象的成員變量時加鎖
永遠只在訪問可變的成員變量時加鎖
永遠不在調用其他對象的方法時加鎖
class Account {
private int balance;
private final Lock lock
= new ReentrantLock();
// 轉賬
void transfer(Account tar, int amt){
while (true) {
if(this.lock.tryLock()) {
try {
if (tar.lock.tryLock()) {
try {
this.balance -= amt;
tar.balance += amt;
} finally {
tar.lock.unlock();
}
}//if
} finally {
this.lock.unlock();
}
}//if
}//while
}//transfer
}
會不會導致死鎖
有可能活鎖土陪,A,B兩賬戶相互轉賬肴熏,各自持有自己lock的鎖旺坠,都一直在嘗試獲取對方的鎖,形成了活鎖
能夠響應中斷扮超, 支持超時 、非阻塞的獲取鎖