19、Java并发性和多线程-嵌套管程锁死
以下內(nèi)容轉(zhuǎn)自http://ifeve.com/nested-monitor-lockout/:
嵌套管程鎖死類似于死鎖, 下面是一個嵌套管程鎖死的場景:
線程1獲得A對象的鎖。 線程1獲得對象B的鎖(同時持有對象A的鎖)。 線程1決定等待另一個線程的信號再繼續(xù)。 線程1調(diào)用B.wait(),從而釋放了B對象上的鎖,但仍然持有對象A的鎖。線程2需要同時持有對象A和對象B的鎖,才能向線程1發(fā)信號。 線程2無法獲得對象A上的鎖,因為對象A上的鎖當前正被線程1持有。 線程2一直被阻塞,等待線程1釋放對象A上的鎖。線程1一直阻塞,等待線程2的信號,因此,不會釋放對象A上的鎖, 而線程2需要對象A上的鎖才能給線程1發(fā)信號……你可以能會說,這是個空想的場景,好吧,讓我們來看看下面這個比較挫的Lock實現(xiàn):
//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操作)當一個已經(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方法的線程都會一直阻塞。這就是嵌套管程鎖死。
一個更現(xiàn)實的例子
你可能會說,這么挫的實現(xiàn)方式我怎么可能會做呢?你或許不會在里層的管程對象上調(diào)用wait或notify方法,但完全有可能會在外層的this上調(diào)。
有很多類似上面例子的情況。例如,如果你準備實現(xiàn)一個公平鎖。你可能希望每個線程在它們各自的QueueObject上調(diào)用wait(),這樣就可以每次喚醒一個線程。
下面是一個比較挫的公平鎖實現(xiàn)方式:
//Fair Lock implementation with nested monitor lockout problem public class FairLock {private boolean isLocked = false;private Thread lockingThread = null;private List waitingThreads = new ArrayList();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。
當一個線程調(diào)用queueObject.wait()方法的時候,它僅僅釋放的是在queueObject對象實例的鎖,并沒有釋放”this”上面的鎖。
現(xiàn)在我們還有一個地方需要特別注意, unlock方法被聲明成了synchronized,這就相當于一個synchronized(this)塊。這就意味著,如果一個線程在lock()中等待,該線程將持有與this關(guān)聯(lián)的管程對象。所有調(diào)用unlock()的線程將會一直保持阻塞,等待著前面那個已經(jīng)獲得this鎖的線程釋放this鎖,但這永遠也發(fā)生不了,因為只有某個線程成功地給lock()中等待的線程發(fā)送了信號,this上的鎖才會釋放,但只有執(zhí)行unlock()方法才會發(fā)送這個信號。
因此,上面的公平鎖的實現(xiàn)會導致嵌套管程鎖死。更好的公平鎖實現(xiàn)方式可以參考Starvation and Fairness。
嵌套管程鎖死 VS 死鎖
嵌套管程鎖死與死鎖很像:都是線程最后被一直阻塞著互相等待。
但是兩者又不完全相同。在死鎖中我們已經(jīng)對死鎖有了個大概的解釋,死鎖通常是因為兩個線程獲取鎖的順序不一致造成的,線程1鎖住A,等待獲取B,線程2已經(jīng)獲取了B,再等待獲取A。如死鎖避免中所說的,死鎖可以通過總是以相同的順序獲取鎖來避免。
但是發(fā)生嵌套管程鎖死時鎖獲取的順序是一致的。線程1獲得A和B,然后釋放B,等待線程2的信號。線程2需要同時獲得A和B,才能向線程1發(fā)送信號。所以,一個線程在等待喚醒,另一個線程在等待想要的鎖被釋放。
不同點歸納如下:
死鎖中,二個線程都在等待對方釋放鎖。嵌套管程鎖死中,線程1持有鎖A,同時等待從線程2發(fā)來的信號,線程2需要鎖A來發(fā)信號給線程1。?
==>如有問題,請聯(lián)系我:easonjim#163.com,或者下方發(fā)表評論。<== 《新程序員》:云原生和全面數(shù)字化實踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的19、Java并发性和多线程-嵌套管程锁死的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: dubbo 视频
- 下一篇: C# Combobox联动