Skip to content

Latest commit

 

History

History
152 lines (111 loc) · 7.14 KB

内置监视器失效.md

File metadata and controls

152 lines (111 loc) · 7.14 KB

####内置监视器失效 #####内置监视器失效是如何发生的 内置监视器失效(nested monitor lockout)是类似于死锁的问题。

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.

上面描述的可能更像一个理论场景,但是,看下面这个简单的Lock实现:

//lock implementation with nested monitor lockout problem

public class Lock{
	protected MonitorObject monitorObject = new MonitorObject();
	protected boolean isLock = 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为true,将不会有什么问题。线程不调用monitorObject.wait()。如果isLocked为true,线程调用lock()将会等待monitorObject.wait()调用。

问题就出在这里,调用monitorObject.wait()仅释放在monitorObject成员变量上的同步监视器,而没有释放关联在"this"上的同步监视器。换句话说,正在等待的那个线程任然持有在"this"对象上的同步锁。

当首次锁住Lock的线程准备通过调用unlock()方法来释放它时,它将会在尝试进入unlock()方法的synchronized(this)代码块时阻塞住。它将会一直阻塞直到等待在lock()里的线程离开synzhronized(this)代码块。但是等待在lock()方法里的线程并不会离开这个代码块,除非isLocked被设置为falsemonitorObject.notify()被执行,这些都发生在unlock()方法中。

Put shortly, the thread waiting in lock() needs an unlock() call to execute successfully for it to exit lock() and the synchronized blocks inside it. But, no thread can actually execute unlock() until the thread waiting in lock() leaves the outer synchronized block.

最后的结果就是,任何一个调用lock()或者unlock()线程都会一直阻塞。这被称为 nested monitor lockout。

#####一个更真实的例子

你可能生成你从来不会去实现像上面那样的锁。你不会在一个内部的锁对象上调用wait()notify()方法。但是,还是有可能碰到这样的情况。例如,如果你在一个锁里实现了公平性。当这么做时,你想每个线程在它们自己的队列对象上调用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();

		aynchronized(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 locks this lock");
			
		}	

		isLocked = false;
		lockingThread = null;
		if(waitingThreads.size() > 0){
			QueueObjct queueObject = waitingThreads.get(0);
			synchronized(queueObject){
				queueObect.notify();
			}
		}
	}
}


public class QueueObject{
    private boolean isNotify = false;

	public synzhronized void doWait() throws InterruptedException{
		if(!isNotify){
		this.isNotify = false;
	}

	public synchronized void doNotify(){
		this.isNoitfy = true;
		this.notify();
	}

	public boolean equals(Object o){
		return (this == o);
	}
}

首先,浏览一下这个实现可能看起来挺好的,但是注意lock()方法是如何调用queueObject.wait()的。queueObject.wait()调用发生在两个同步代码块中。一个是在“this”上的同步,乞讨在它里面的是一个在queueObject局部变量上的同步代码块。当一个线程调用queueObject.wait()时,它释放在queueObject上的锁。但不是与"this"关联的锁。

同时,我们也注意到,unlock()方法被声明为synchronized,等同于一个synchronized(this)代码块。这意味着,如果一个线程在lock()方法里正在等待与“this”关联的监视器对象就会被这个阻塞线程等待。所有调用unlock()方法就会一直阻塞等待那个正在等待的线程释放掉在“this”上的锁。但这永远也不会发生,因为这仅仅在一个线程成功向那个正在等待的线程发送一个信号才会发生,而只有在执行unlock()方法时才会发送这样的信号。

综上所述,上面公平锁的实现可能会导致内置监视器失效(nested monitor lockout)。

#####内置监视器失效 vs. 死锁

内置监视器失效和死锁的结果趋近于相同:线程间彼此一直等待。

这两种场景也不完全相同。当两个线程以不同的顺序去获取锁时就会发生死锁,线程1锁住A,等待B。线程2已经锁住B,但是现在等待A。我们可以通过维护锁的顺序来避免死锁。然而,当两个线程以相同的顺序获得锁时就会发生内置监视器失效。线程1锁住了A和B,然后等待一个来自线程B的信号释放B。线程2又需要同时持有A和B才能向线程1发信号。因此,一个线程在等待信号,另一个在等待锁被释放。

差异总结如下:

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.