====== Nested Monitor Problem ====== ''' 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 (); } } } }