java:nested_lockeout

Αδιέξοδο λόγο χρήσης εμφωλευμένων συγχρονισμένων μεθόδων ή μπλοκ

Το αδιέξοδο χρήσης εμφωλευμένων συγχρονισμένων μεθόδων ή μπλοκ είναι αντίστοιχο με το deadlock αν και ο λόγος για τον οποίο συμβαίνει είναι διαφορετικός. Δείτε το παρακάτω παράδειγμα “κλειδαριάς”.

Lock.java
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() πρώτα λαμβάνει το monitor lock του αντικειμένου “this” και στη συνέχεια λαμβάνει το monitor lock του αντικειμένου monitorObject. Εάν η κλειδαριά δεν είναι κλειδωμένη δηλ (isLocked!=true) τότε το νήμα δεν μπαίνει μέσα στο while() και δεν λαμβάνει και το 2ο lock. Αν όμως η κλειδαριά είναι κλειδωμένη τότε το νήμα λαμβάνει και το 2ο lock και κοιμάται μέσα στην μέθοδο wait() περιμένοντας κάποιο άλλο νήμα να το ξυπνήσει.

Το πρόβλημα με το παραπάνω σχήμα είναι ότι το νήμα που κοιμάται ελευθερώνει το monitor lock που συνδέεται με το monitorObject, αλλά δεν ελευθερώνει το monitor lock που συνδέεται με το this object. Έτσι όταν ένα άλλο νήμα θα προσπαθήσει να ξεκλειδώσει την κλειδαριά μέσω της unlock() θα μπλοκάρει μέσα στον κώδικα

    synchronized(this){
      this.isLocked = false;
      synchronized(this.monitorObject){
        this.monitorObject.notify();
      }
    }

Παρακάτω δίνεται ένα πιο ρεαλιστικό παράδειγμα με βάση την κλάση FairLock.

FairLock.java
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();
      }
    }
  }
}
java/nested_lockeout.txt · Last modified: 2016/02/26 11:15 (external edit)