7. 嵌套管程锁死&Slipped Condition

场景介绍

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();
      }
    }
  }
}
  1. lock()方法在this上同步,然后在monitorObject上同步。如果isLocked为true,调用lock()方法的线程会在wait()方法上阻塞。
  2. 如果此时需要unlock,由于wait()只释放了monitorObject的锁,this相关的管程对象并没有释放,所以unlock()方法等待lock()方法释放锁,而lock()方法必须等待unlock()方法的isLocked变为false。

简而言之,在lock方法中等待的线程需要其它线程成功调用unlock方法来退出lock方法,但是,在lock()方法离开外层同步块之前,没有线程能成功执行unlock()。

另一个例子

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();
      }
    }
  }
}
  1. 方法内部有两个synchronized块,一个锁定this,一个嵌在上一个synchronized块内部,它锁定的是局部变量queueObject。
  2. 当一个线程调用queueObject.wait()方法的时候,它仅仅释放的是在queueObject对象实例的锁,并没有释放”this”上面的锁。
  3. unlock方法被声明成了synchronized,这就相当于一个synchronized(this)块。这就意味着,如果一个线程在lock()中等待,该线程将持有与this关联的管程对象。所有调用unlock()的线程将会一直保持阻塞,等待着前面那个已经获得this锁的线程释放this锁,但这永远也发生不了,因为只有某个线程成功地给lock()中等待的线程发送了信号,this上的锁才会释放,但只有执行unlock()方法才会发送这个信号。

嵌套管程锁死VS死锁

管程:获取锁的顺序是一致的。线程1获取A和B,然后释放B,等待2的信号。线程2需要同时获得A和B,才能向1发送信号。一个线程等待唤醒,另一个线程等待想要的锁被释放。

死锁:两个线程获取锁的顺序不一致,两个线程都在等待对方释放锁。

Slipped Condition

slipped condition是说从一个线程检查一个特定条件到该线程操作此条件期间,这个条件已经被其他线程改变,导致第一个线程在该条件上发生了错误的操作。

public class Lock {

    private boolean isLocked = true;

    public void lock(){
      synchronized(this){
        while(isLocked){
          try{
            this.wait();
          } catch(InterruptedException e){
            //do nothing, keep waiting
          }
        }
      }

      synchronized(this){
        isLocked = true;
      }
    }

    public synchronized void unlock(){
      isLocked = false;
      this.notify();
    }

}

如果线程A先进入第一个lock同步块,这时候发现isLock为false。此时第二个线程执行,也进入第一个同步块,同样发现isLocked是false。此时两个线程都检查了这个条件,然后回继续进入到第二个同步快并设置isLocked为true。

修复方法:将isLocked = true移到第一个同步块中。

问题:变量的检查与赋值应该在同一个同步块中完成。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容