【Java併發性和多線程】嵌套管程鎖死

本文爲轉載學習html

原文連接:http://ifeve.com/nested-monitor-lockout/java

嵌套管程鎖死相似於死鎖, 下面是一個嵌套管程鎖死的場景:學習

線程1得到A對象的鎖。this

線程1得到對象B的鎖(同時持有對象A的鎖)。spa

線程1決定等待另外一個線程的信號再繼續。線程

線程1調用B.wait(),從而釋放了B對象上的鎖,但仍然持有對象A的鎖。code


線程2須要同時持有對象A和對象B的鎖,才能向線程1發信號。htm

線程2沒法得到對象A上的鎖,由於對象A上的鎖當前正被線程1持有。對象

線程2一直被阻塞,等待線程1釋放對象A上的鎖。rem


線程1一直阻塞,等待線程2的信號,所以,不會釋放對象A上的鎖,

而線程2須要對象A上的鎖才能給線程1發信號……


你能夠能會說,這是個空想的場景,好吧,讓咱們來看看下面這個比較挫的Lock實現:

//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,由於線程不會繼續調用monitorObject.wait(),那麼一切都沒有問題 。可是若是isLocked等於true,調用lock()方法的線程會在monitorObject.wait()上阻塞。

這裏的問題在於,調用monitorObject.wait()方法只釋放了monitorObject上的管程對象,而與」this「關聯的管程對象並無釋放。換句話說,這個剛被阻塞的線程仍然持有」this」上的鎖。

校對注:若是一個線程持有這種Lock的時候另外一個線程執行了lock操做)當一個已經持有這種Lock的線程想調用unlock(),就會在unlock()方法進入synchronized(this)塊時阻塞。這會一直阻塞到在lock()方法中等待的線程離開synchronized(this)塊。可是,在unlock中isLocked變爲false,monitorObject.notify()被執行以後,lock()中等待的線程纔會離開synchronized(this)塊。

簡而言之,在lock方法中等待的線程須要其它線程成功調用unlock方法來退出lock方法,可是,在lock()方法離開外層同步塊以前,沒有線程能成功執行unlock()。

結果就是,任何調用lock方法或unlock方法的線程都會一直阻塞。這就是嵌套管程鎖死。

一個更現實的例子

你可能會說,這麼挫的實現方式我怎麼可能會作呢?你或許不會在裏層的管程對象上調用wait或notify方法,但徹底有可能會在外層的this上調。
有不少相似上面例子的狀況。例如,若是你準備實現一個公平鎖。你可能但願每一個線程在它們各自的QueueObject上調用wait(),這樣就能夠每次喚醒一個線程。

下面是一個比較挫的公平鎖實現方式:

//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方法是怎麼調用queueObject.wait()的,在方法內部有兩個synchronized塊,一個鎖定this,一個嵌在上一個synchronized塊內部,它鎖定的是局部變量queueObject。
當一個線程調用queueObject.wait()方法的時候,它僅僅釋放的是在queueObject對象實例的鎖,並無釋放」this」上面的鎖。

如今咱們還有一個地方須要特別注意, unlock方法被聲明成了synchronized,這就至關於一個synchronized(this)塊。這就意味着,若是一個線程在lock()中等待,該線程將持有與this關聯的管程對象。全部調用unlock()的線程將會一直保持阻塞,等待着前面那個已經得到this鎖的線程釋放this鎖,但這永遠也發生不了,由於只有某個線程成功地給lock()中等待的線程發送了信號,this上的鎖纔會釋放,但只有執行unlock()方法纔會發送這個信號。

所以,上面的公平鎖的實現會致使嵌套管程鎖死。更好的公平鎖實現方式能夠參考Starvation and Fairness

嵌套管程鎖死 VS 死鎖

嵌套管程鎖死與死鎖很像:都是線程最後被一直阻塞着互相等待。

可是二者又不徹底相同。在死鎖中咱們已經對死鎖有了個大概的解釋,死鎖一般是由於兩個線程獲取鎖的順序不一致形成的,線程1鎖住A,等待獲取B,線程2已經獲取了B,再等待獲取A。如死鎖避免中所說的,死鎖能夠經過老是以相同的順序獲取鎖來避免。
可是發生嵌套管程鎖死時鎖獲取的順序是一致的。線程1得到A和B,而後釋放B,等待線程2的信號。線程2須要同時得到A和B,才能向線程1發送信號。因此,一個線程在等待喚醒,另外一個線程在等待想要的鎖被釋放。

不一樣點概括以下:

死鎖中,二個線程都在等待對方釋放鎖。


嵌套管程鎖死中,線程1持有鎖A,同時等待從線程2發來的信號,線程2須要鎖A來發信號給線程1。

相關文章
相關標籤/搜索