歡迎來到《王者並行課》,本文是該系列文章中的第16篇。
在前面的文章《鉑金1:探本溯源-為何說Lock介面是Java中鎖的基礎》中,我們提到了鎖的可重入問題,並作了簡單介紹。鑑於鎖的可重入是一個重要概念,所以本文把拿出來做一次單獨講解,以幫助你徹底理解它。
首先,我們通過一段範例程式碼看鎖的可重入是如何導致問題發生,以理解它的重要性。
public class ReentrantWildArea {
// 野區鎖定
private boolean isAreaLocked = false;
// 進入野區A
public synchronized void enterAreaA() throws InterruptedException {
isAreaLocked = true;
System.out.println("已經進入野區A...");
enterAreaB();
}
// 進入野區B
public synchronized void enterAreaB() throws InterruptedException {
while (isAreaLocked) {
System.out.println("野區B方法進入等待中...");
wait();
}
System.out.println("已經進入野區B...");
}
public synchronized void unlock() {
isAreaLocked = false;
notify();
}
}
在上面這段程式碼中,我們建立了一片野區,包含了野區A和野區B。接著,我們再建立一個打野英雄鎧,讓他進去野區打野,看看會發生什麼事情。
public static void main(String[] args) {
// 打野英雄鎧進入野區
Thread kaiThread = new Thread(() -> {
ReentrantWildArea wildArea = new ReentrantWildArea();
try {
wildArea.enterAreaA();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
kaiThread.start();
}
輸出結果如下:
已經進入野區A...
野區B方法進入等待中...
從結果中可以看到,雖然在同一塊野區,但是鎧只進入野區A,卻沒能進入野區B,被阻塞在半道上了。從程式碼分析上看,野區的兩個方法都宣告了synchronized
,但鎧在進入野區A之後,野區進行了鎖定isAreaLocked = true
,導致鎧進入野區B時失敗。
這就是典型的鎖的可重入所造成的問題。在並行程式設計時,如果未能處理好這一問題,將會造成執行緒的無限阻塞,其後果和死鎖相當。
所謂鎖的可重入,指的是鎖可以被執行緒 重複 或 遞迴 呼叫,也可以理解為對同一把鎖的重複獲取。 如果未能處理好鎖的可重入問題,將會導致和死鎖類似的問題。
避免鎖的可重入問題,需要注意兩個方面:
在Java中,synchronized
是可以重入的,下面的這段程式碼在呼叫時不會產生重入問題。
public class WildMonster {
public synchronized void A() {
B();
}
public synchronized void B() {
doSomething...
}
}
但是,基於Lock介面所實現的各種鎖並不總是支援可重入的。在前面的文章中,我們已經展示過不支援重入的Lock介面實現。在具體的場景中使用時,需要務必注意這點。如果需要可重入鎖,可以使用Java中的ReentrantLock類。
在本文中,我們再次介紹了鎖的可重入問題,並介紹了其產生的原因及避免方式。Java中的synchronized
關鍵字支援鎖的可重入,但是其他顯示鎖並非總是支援這一特性,在使用時需要注意。
此外,需要注意的是,鎖的可重入對鎖的效能有一定的影響,而且實現起來更為複雜。所以,我們不能說鎖的可重入與不可重入哪個好,這要取決於具體的問題。
正文到此結束,恭喜你又上了一顆星✨
從業近十年,先後從事敏捷與DevOps諮詢、Tech Leader和管理等工作,對分散式高並行架構有豐富的實戰經驗。熱衷於技術分享和特定領域書籍翻譯,掘金小冊《高並行秒殺的設計精要與實現》作者。
關注公眾號【MetaThoughts】,及時獲取文章更新和文稿。
如果本文對你有幫助,歡迎點贊、關注、監督,我們一起從青銅到王者。