嵌套监视器锁定
嵌套监视器锁定如何发生
嵌套监视器锁定是一个类似于死锁的问题。发生嵌套监视器锁定,如下所示:
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 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"为假,则没有问题。线程不调用monitorObject.wait()。但是,如果" isLocked"为true,则将调用" lock()"的线程停放在" monitorObject.wait()"调用中。
这样做的问题是,对monitorObject.wait()的调用仅释放了monitorObject成员上的同步监视器,而不释放与" this"相关联的同步监视器。换句话说,刚刚停在等待状态的线程仍将同步锁保持在" this"上。
当首先锁定" Lock"的线程试图通过调用" unlock()"将其解锁时,试图在" unlock()方法"中输入" synchronized(this)"块将被阻止。它将一直处于阻塞状态,直到在" lock()"中等待的线程离开" synchronized(this)"块为止。但是等待lock()方法的线程不会离开该块,直到将isLocked设置为false,并执行monitorObject.notify(),就像在unlock()中那样。
简而言之,等待lock()的线程需要执行unlock()调用才能成功执行,以退出lock()和其中的同步块。但是,直到在lock()中等待的线程离开外部同步块之前,没有线程可以实际执行unlock()。
结果是任何调用lock()或者unlock()的线程都会被不确定地阻塞。这称为嵌套监视器锁定。
一个更现实的例子
我们可能声称我们将永远不会实现前面显示的那种锁。我们不会在内部监控器对象上调用wait()和notify(),而是在上调用。这可能是正确的。但是在某些情况下,可能会出现上述一种设计。例如,如果要在Lock中实现公平性。这样做时,我们希望每个线程在各自的队列对象上调用" 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 = waitingThreads.get(0);
synchronized(queueObject){
queueObject.notify();
}
}
}
}
public class QueueObject {}
乍一看,这种实现看起来不错,但是请注意lock()方法如何从两个同步块内部调用queueObject.wait();。一个在" this"上同步,并嵌套其中,在" queueObject"局部变量上同步一个块。当线程调用" queueObject.wait()"时,它释放" QueueObject"实例上的锁,但不释放与" this"关联的锁。
还要注意,unlock()方法被声明为synchronized,它等于一个synchronized(this)块。这意味着,如果一个线程在" lock()"内部等待,则与" this"关联的监视对象将被等待的线程锁定。所有调用unlock()的线程将无限期地保持阻塞状态,等待正在等待的线程释放对" this"的锁定。但这永远不会发生,因为只有在线程成功向等待的线程发送信号时才会发生,并且只能通过执行unlock()方法来发送。
因此,上面的FairLock实现可能导致嵌套的监视器锁定。文本饥饿和公平中描述了公平锁定的更好实现。
嵌套监视器锁定与死锁
嵌套监视器锁定和死锁的结果几乎相同:所涉及的线程最终永远被阻塞,彼此等待。
但这两种情况并不相等。如关于死锁的文本中所述,当两个线程以不同顺序获得锁时,就会发生死锁。线程1锁定A,等待B。线程2锁定B,现在等待A。如防死锁中的文本所述,可以通过始终以相同顺序锁定锁(锁定顺序)来避免死锁。但是,嵌套的监视器锁定完全由两个线程以相同顺序进行锁定而发生。线程1锁定A和B,然后释放B并等待来自线程2的信号。线程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.

