====== 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 ();
}
}
}
}