martedì 31 gennaio 2012

Nested Monitor Problem

Leggi l'articolo completo su flaviocdc.net 

' Versione da riguardare '
' A cura di Flavio Casadei Della Chiesa '
Il Nested Monitor Problem (NMP) si ha in genere quando un Thread entrando nella coda di wait di un oggetto “mette a dormire con se” il lock dell'unico oggetto che può “risvegliare” tale Thread tramite una notify*().
In generale è buona norma 'non' mettere un Thread in una coda di wait se non si è sicuri che quancun altro Thread sia in grado di eseguire una notify* su di essa (e quindi liberare i Thread in attesa). A volte nonostante sia stato predisposto un adeguato sistema di metodi di notifica e attesa può accadere che il metodo di notifica non possa essere eseguito in quanto il suo accesso è “sbarrato” da un lock che non potrà mai essere acquisito. Vediamo un esempio di NMP
class Inner {
  protected boolean cond = false;
  public synchronized void await ()   {
    while (!cond)
      try { wait (); }
      catch (InterruptedException e) {}
      // .....
  }
  public synchronized void signal (boolean c)   {
    cond = c;
    notifyAll ();
  }
}
class Outer {
  protected Inner inner_ = new Inner (); // nota! non è accessibile all'esterno
  public synchronized void process ()   {
    inner_.await ();
  }
  public synchronized void set (boolean c)   {
    inner_.signal (c);
  }
}
Nella classe denominata Inner i metodi di attesa e notifica sono giustamente messi pubblici e synchronized; la classe Outer si limita solamente ad utilizzare tali metodi in blocchi sincronizzati al fine di evitare accessi concorrenti. A prima vista sembrerebbe tutto a posto tuttavia in questo frammento di codice si cela un'insidia poco visibile.
Prima di entrare nella coda di wait di un oggetto X, un generico Thread T ne deve acquisire il lock intrinseco (senza se e senza ma), una volta entrato in wait tale lock viene rilasciato e messo a disposizione di altri Thread. Fin qua niente di nuovo, tuttavia se leggiamo attentamente non ho scritto che 'tutti' i lock acquisiti dal Thread T vengono rilasciati; ed infatti solo il lock intrinseco viene rilasciato, gli altri no. Se osserviamo il NMP da vicino si nota che per un Thread T1 che invoca Outer.process() acquisisce ben due lock: quello di Outer e quello di Inner, ma una volta entrato in wait (su inner) il lock di Outer rimane acquisito e non rilasciato. Ogni altro Thread T2 che prova ad invocare Outer.set() rimane bloccato all'infinito in quanto il lock di Outer è in possesso di T1. Non esiste quindi alcun modo per riacquisire il lock di Outer in quanto nessun Thread è in grado di eseguire la notify().
La Nested in Nested Monitor Problem deriva dal fatto che la classe che esegue la wait di norma è una classe interna/annidata della classe che espone i metodi ai client; questa di norma è una buona tecnica di confinamento che limita alcuni problemi di concorrenza anche se, come abbiamo visto, può generare probkemi ben più gravi.
public class Outer {
        protected Nested _nested = new Nested (); // nota! non è accessibile all'esterno
        public synchronized void process ()   {
                _nested.await ();
        }
        public synchronized void set (boolean c)   {
                _nested.signal (c);
        }
        private class Nested {
                protected boolean cond = false;
                public synchronized void await ()   {
                        while (!cond)
                                try { wait (); }
                        catch (InterruptedException e) {}
                        // .....
                }
                public synchronized void signal (boolean c)   {
                        cond = c;
                        notifyAll ();
                }
        }
}

Nested Monitor Solution?
Beh le soluzioni sono molte e non tutte applcabili in ogni contesto. Analizziamo una ipotetica soluzione ne propongo una basata su classi annidate (nested)
public class NestedMonitor {
        private final Nested _nested = new Nested (); // nota! non è accessibile all'esterno
        public  void process ()   {
                _nested.await (); // nessun lock acquisito
        }
        public  void set (boolean c)   {
                _nested.signal (c); // nessun lock acquisito
        }
        private class Nested {
                protected boolean cond = false;
                public synchronized void await ()   {
                        while (!cond)
                                try { wait (); }
                        catch (InterruptedException e) {Thread.currentThread().interrupt(); return ;}
                        // .....
                }
                public synchronized void signal (boolean c)   {
                        cond = c;
                        notifyAll ();
                }
        }
}
Come si nota, la soluzione consiste nel rimuovere la keyword synchronized dai metodi della classe Outer, più qualche “aggiunta” ….. Non si notano a prima vista grossi problemi, ma riflettendo meglio non è esclusa la possibilità di generare il solito problema nel caso in cui una classe cliente tenti un client side locking su una istanza della classe Nestedmonitor.
Attenzione quindi a chiamare metodi “alieni” con un lock acquisito!
Propongo due versioni alternative di una ipotetica soluzione, presto seguiranno commenti … spero pure i vostri! Contattatemi pure all'indirizzo fcasadei CHIOCCIOLA gmail PUNTO com
public class Outer {
        private final Nested _nested = new Nested (); // nota! non è accessibile all'esterno
        private final Object lock = new Object();
        public  void process ()   {
                _nested.await (); // nessun lock acquisito
        }
        public  void set (boolean c)   {
                _nested.signal (c); // nessun lock acquisito
        }
        private class Nested {
                protected boolean cond = false;
                public  void await ()   {
                        synchronized (lock) { //prendo il lock "giusto"
                                while (!cond)
                                        try { lock.wait (); }
                                catch (InterruptedException e) {Thread.currentThread().interrupt(); return ;}
                                // .....
                        }
                }
 
                public  void signal (boolean c)   {
                        synchronized (lock) {
                                cond = c;
                                lock.notifyAll ();
                        }
                }
        }
}
public class NestedOuter {
        private final Nested _nested = new Nested (); // nota! non è accessibile all'esterno
 
        public  void process ()   {
                _nested.await (); // nessun lock acquisito
        }
        public  void set (boolean c)   {
                _nested.signal (c); // nessun lock acquisito
        }
        private class Nested {
                protected boolean cond = false;
                public  void await ()   {
                        synchronized (NestedOuter.this) { //prendo il lock "giusto"
                                while (!cond)
                                        try { NestedOuter.this.wait (); }
                                catch (InterruptedException e) {Thread.currentThread().interrupt(); return ;}
                                // .....
                        }
                }
 
                public  void signal (boolean c)   {
                        synchronized (NestedOuter.this) {
                                cond = c;
                                NestedOuter.this.notifyAll ();
                        }
                }
        }
}

Nessun commento:

Posta un commento