Java并發(fā)之嵌套管程鎖死詳解
·嵌套管程死鎖是如何發(fā)生的
·具體的嵌套管程死鎖的例子
·嵌套管程死鎖 vs 死鎖
嵌套管程鎖死類似于死鎖, 下面是一個嵌套管程鎖死的場景:
Thread 1 synchronizes on A
Thread 1 synchronizes on B (while synchronized on A)
Thread 1 decides to wait for a signal from another thread before continuing
Thread 1 calls B.wait() thereby releasing the lock on B, but not A.
Thread 2 needs to lock both A and B (in that sequence)
to send Thread 1 the signal.
Thread 2 cannot lock A, since Thread 1 still holds the lock on A.
Thread 2 remain blocked indefinately waiting for Thread1
to release the lock on A
Thread 1 remain blocked indefinately waiting for the signal from
Thread 2, thereby
never releasing the lock on A, that must be released to make
it possible for Thread 2 to send the signal to Thread 1, etc.
線程1獲得A對象的鎖。
線程1獲得對象B的鎖(同時持有對象A的鎖)。
線程1決定等待另一個線程的信號再繼續(xù)。
線程1調(diào)用B.wait(),從而釋放了B對象上的鎖,但仍然持有對象A的鎖。
線程2需要同時持有對象A和對象B的鎖,才能向線程1發(fā)信號。
線程2無法獲得對象A上的鎖,因為對象A上的鎖當(dāng)前正被線程1持有。
線程2一直被阻塞,等待線程1釋放對象A上的鎖。
線程1一直阻塞,等待線程2的信號,因此,不會釋放對象A上的鎖,
而線程2需要對象A上的鎖才能給線程1發(fā)信號……
我們看下面這個實際的例子:
//lock implementation with nested monitor lockout problem
public class Lock{
protected MonitorObject monitorObject = new MonitorObject();
protected boolean isLocked = false;
public void lock() throws InterruptedException{
synchronized(this){
while(isLocked){
synchronized(this.monitorObject){
this.monitorObject.wait();
}
}
isLocked = true;
}
}
public void unlock(){
synchronized(this){
this.isLocked = false;
synchronized(this.monitorObject){
this.monitorObject.notify();
}
}
}
}
可以看到,lock()方法首先在”this”上同步,然后在monitorObject上同步。如果isLocked等于false,因為線程不會繼續(xù)調(diào)用monitorObject.wait(),那么一切都沒有問題 。但是如果isLocked等于true,調(diào)用lock()方法的線程會在monitorObject.wait()上阻塞。
這里的問題在于,調(diào)用monitorObject.wait()方法只釋放了monitorObject上的管程對象,而與”this“關(guān)聯(lián)的管程對象并沒有釋放。換句話說,這個剛被阻塞的線程仍然持有”this”上的鎖。
(校對注:如果一個線程持有這種Lock的時候另一個線程執(zhí)行了lock操作)當(dāng)一個已經(jīng)持有這種Lock的線程想調(diào)用unlock(),就會在unlock()方法進入synchronized(this)塊時阻塞。這會一直阻塞到在lock()方法中等待的線程離開synchronized(this)塊。但是,在unlock中isLocked變?yōu)閒alse,monitorObject.notify()被執(zhí)行之后,lock()中等待的線程才會離開synchronized(this)塊。
簡而言之,在lock方法中等待的線程需要其它線程成功調(diào)用unlock方法來退出lock方法,但是,在lock()方法離開外層同步塊之前,沒有線程能成功執(zhí)行unlock()。
結(jié)果就是,任何調(diào)用lock方法或unlock方法的線程都會一直阻塞。這就是嵌套管程鎖死。
具體的嵌套管程死鎖的例子
例如,如果你準(zhǔn)備實現(xiàn)一個公平鎖。你可能希望每個線程在它們各自的QueueObject上調(diào)用wait(),這樣就可以每次喚醒一個線程。
//Fair Lock implementation with nested monitor lockout problem
public class FairLock {
private boolean isLocked = false;
private Thread lockingThread = null;
private List<QueueObject> waitingThreads =
new ArrayList<QueueObject>();
public void lock() throws InterruptedException{
QueueObject queueObject = new QueueObject();
synchronized(this){
waitingThreads.add(queueObject);
while(isLocked || waitingThreads.get(0) != queueObject){
synchronized(queueObject){
try{
queueObject.wait();
}catch(InterruptedException e){
waitingThreads.remove(queueObject);
throw e;
}
}
}
waitingThreads.remove(queueObject);
isLocked = true;
lockingThread = Thread.currentThread();
}
}
public synchronized void unlock(){
if(this.lockingThread != Thread.currentThread()){
throw new IllegalMonitorStateException(
"Calling thread has not locked this lock");
}
isLocked = false;
lockingThread = null;
if(waitingThreads.size() > 0){
QueueObject queueObject = waitingThread.get(0);
synchronized(queueObject){
queueObject.notify();
}
}
}
}
乍看之下,嗯,很好,但是請注意lock方法是怎么調(diào)用queueObject.wait()的,在方法內(nèi)部有兩個synchronized塊,一個鎖定this,一個嵌在上一個synchronized塊內(nèi)部,它鎖定的是局部變量queueObject。
當(dāng)一個線程調(diào)用queueObject.wait()方法的時候,它僅僅釋放的是在queueObject對象實例的鎖,并沒有釋放”this”上面的鎖。
現(xiàn)在我們還有一個地方需要特別注意, unlock方法被聲明成了synchronized,這就相當(dāng)于一個synchronized(this)塊。這就意味著,如果一個線程在lock()中等待,該線程將持有與this關(guān)聯(lián)的管程對象。所有調(diào)用unlock()的線程將會一直保持阻塞,等待著前面那個已經(jīng)獲得this鎖的線程釋放this鎖,但這永遠也發(fā)生不了,因為只有某個線程成功地給lock()中等待的線程發(fā)送了信號,this上的鎖才會釋放,但只有執(zhí)行unlock()方法才會發(fā)送這個信號。
因此,上面的公平鎖的實現(xiàn)會導(dǎo)致嵌套管程鎖死。
Nested Monitor Lockout vs. Deadlock
嵌套管程鎖死與死鎖很像:都是線程最后被一直阻塞著互相等待。
但是兩者又不完全相同。在死鎖中我們已經(jīng)對死鎖有了個大概的解釋,死鎖通常是因為兩個線程獲取鎖的順序不一致造成的,線程1鎖住A,等待獲取B,線程2已經(jīng)獲取了B,再等待獲取A。如死鎖避免中所說的,死鎖可以通過總是以相同的順序獲取鎖來避免。但是發(fā)生嵌套管程鎖死時鎖獲取的順序是一致的。線程1獲得A和B,然后釋放B,等待線程2的信號。線程2需要同時獲得A和B,才能向線程1發(fā)送信號。所以,一個線程在等待喚醒,另一個線程在等待想要的鎖被釋放。
不同點歸納如下:
In deadlock, two threads are waiting for each other to release locks. In nested monitor lockout, Thread 1 is holding a lock A, and waits for a signal from Thread 2. Thread 2 needs the lock A to send the signal to Thread 1.
死鎖中,二個線程都在等待對方釋放鎖。
嵌套管程鎖死中,線程1持有鎖A,同時等待從線程2發(fā)來的信號,線程2需要鎖A來發(fā)信號給線程1。
總結(jié)
以上就是本文關(guān)于Java并發(fā)之嵌套管程鎖死詳解的全部內(nèi)容,希望對大家有所幫助。感興趣的朋友可以繼續(xù)參閱本站:java并發(fā)編程之cas詳解、java實現(xiàn)遍歷樹形菜單兩種實現(xiàn)代碼分享等,有什么問題可以隨時留言,小編會及時回復(fù)大家的。感謝朋友們對本站的支持!
相關(guān)文章
Mybatis-Plus中的MetaObjectHandler組件的使用
MetaObjectHandler是Mybatis-Plus中一個實用組件,專門用于自動處理實體對象中的特定字段,如創(chuàng)建時間、更新時間、創(chuàng)建人和修改人等,該接口允許開發(fā)者在不修改業(yè)務(wù)代碼的情況下,實現(xiàn)自動填充功能,極大地簡化了代碼的復(fù)雜性,感興趣的可以了解一下2024-10-10
Jenkins 關(guān)閉和重啟詳細(xì)介紹及實現(xiàn)
這篇文章主要介紹了Jenkins的關(guān)閉、重啟的相關(guān)資料,用jar -jar jenkins.war來啟動jenkins服務(wù)器,那么我們?nèi)绾侮P(guān)閉或者重啟jenkins服務(wù)器呢,這里就給出實現(xiàn)的方法,需要的朋友可以參考下2016-11-11
SpringBoot超詳細(xì)講解@Enable*注解和@Import
這篇文章主要介紹了SpringBoot?@Enable*注解和@Import,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07
詳解Spring MVC/Boot 統(tǒng)一異常處理最佳實踐
在 Web 開發(fā)中, 我們經(jīng)常會需要處理各種異常,這篇文章主要介紹了詳解Spring MVC/Boot 統(tǒng)一異常處理最佳實踐,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-01-01
不寫mybatis的@Param有的報錯有的卻不報錯問題分析
這篇文章主要為大家介紹了不寫mybatis的@Param有的報錯有的卻不報錯問題分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-09-09
Java中如何使用?byte?數(shù)組作為?Map?的?key
本文將討論在使用HashMap時,當(dāng)byte數(shù)組作為key時所遇到的問題及其解決方案,介紹使用String和List這兩種數(shù)據(jù)結(jié)構(gòu)作為臨時解決方案的方法,感興趣的朋友跟隨小編一起看看吧2023-06-06

