Newsgroup: it.comp.java
Titolo: Wait, Thread e Notify

Ciao a tutti,
mi sono incagliato in un problema. Voglio che i miei thread notifichino al
thread chiamante il verificarsi di una condizione. Ho "scoperto" i metodi
wait del chiamante e notify per i thread ma non sono riuscito a capirci
molto.
Suggerimenti ?
Grazie
 Ciao
   Saman


Da quello che dici mi sembra di capire che il problema è solo di
avvisare il thread chiamante del sopraggiungere di un evento; in
pratica il thread chiamante diventa un semplice "osservatore" dello
stato del thread corrente; allora ti basta implementare un pattern
Observer. Detto in parole povere, devi fare in modo che il thread
corrente abbia un riferimento al thread chiamante, e ogni volta che si
verifica un particolare evento chiamerà un metodo particolare e noto
del thread chiamante, in cui avverrà la gestione di tale evento.
Ad esempio, supponendo che il thread padre (intendo quello che deve
essere avvisato di eventi generati dall'altro thread, da me
simbolicamente chiamato Thread figlio) debba essere avvisato di un
evento chiamato MyEvent, bisognerebbe craere un'apposita interfaccia e
aggiungere alle classi i seguenti metodi e variabili:
interface MyEventListener {
  public void myEventOccurred( MyEvent ev);
}
class FatherThread  implements MyEventListener{
   ..... tutti i tuoi metodi e variabili....
   public void myEventOccurred(MyEvent ev) {
     // do whatever you need
   }   
}
class SonThread {
  ... tutti i tuoi metodi e variabili ....
  ... con l'avvertenza di prevedere un metodo per inizializzare
  ... m_listener oppure di inizializzarlo direttamente nel costruttore
  ... con l'istruzione:
  ...      m_listener = (MyEventListener) father; 
  ... dove "father" è un istanza di tipo FatherThread... 
  MyEventListener m_listener;
  metodo_che_genera_eventi() {
       // do something until a new event occurres
       MyEvent ev  = new MyEvent(......);
       listener.myEventOccurred(ev);  // notifica asincrona
                                      // dell'evento arrivato
  }  
}
Ovviamente ho semplificato, ma guarda bene come avviene la notifica
eventi nel modello AWT (versione superiore alla 1.0); ad esempio
guarda la notifica eventi per la gestione del messaggio di chiusura
della finestra, che è il più semplice da capire e il più comune da
usare.
Se invece il tuo problema implica che il thread chiamante resti
bloccato in attesa del verificarsi di un particolare evento da parte
di un altro thread, puoi tranquillamente usare i metodi wait e notify.
In questo caso ti servirà definire un'ulteriore interfaccia, che renda
visibile al thread figlio solo il metodo che risveglia il thread padre
(sempre per questioni di information hiding). "wait" serve a bloccare
il thread cui appartiene, e "notify" a riprenderne l'esecuzione dal
punto in cui si era bloccato; entrambi devono essere richiamati in
forma sincrona (cioè in blocchi sincronizzati sull'istanza del
relativo thread, oppure in metodi sincronizzati appartenenti alla
istanza del thread cui si riferiscono).
In questo esempio ho cercato di creare 2 classi di tipo thread,
un'interfaccia per gestire l'evento interessato, e una classe TestWait
per verificare il funzionamento (le 2 classi estese da Thread
dovrebbero rappresentare i tuoi 2 thread di tipo diverso in grado
comunque di comunicare, poi sta a te adattare al tuo caso questo
esempio elementare); ogni volta che premi invio da tastiera verrà
risvegliato il thread di tipo FatherThread e verranno stampati su
video in sequenza i metodi richiamati; subito dopo essersi svegliatoe
aver notificato a video l'arrivo di un evento, l'oggetto di tipo
FatherThread ritorna a dormire in attesa di un nuovo evento.
Digitando "quit" e poi invio si blocca il tutto.
public class TestWait {
  public static void main (String argv[]) {
    FatherThread ft = new FatherThread();
    ft.start();
    SonThread st = new SonThread(ft);
    st.start();
    try {  // continua a mandare eventi ogni qualvolta premo invio
      byte buf[] = new byte[100];
      int iIter = 0;
      for (;;) {
        System.out.println("\nLoop iteration: " + ++iIter);
        System.in.read(buf,0,100);
        if (new String(buf).trim().equals("quit")) break;
        st.run();
      }
    } catch(Exception ex) {ex.printStackTrace();}
    ft.stopThread(); // fa morire il FatherThread senza richiamare il
metodo deprecato stop()
  }
}
interface AwakeEventListener {
  public void awakeThread();
}
class FatherThread extends Thread implements AwakeEventListener {
  protected boolean m_bStop;
  protected boolean m_bLoop;
  public synchronized void stopThread() {
    System.out.println("StopThread");
    m_bLoop = false;
    awakeThread();
  }
  protected synchronized void BlockingMethod() {
    System.out.println("BlockingMethod");
    m_bStop = true;
    while (m_bStop) {
      try {
        wait();   // a questo punto solo una notify potrà far
                // risvegliare il tuo thread, che tornerà a dormire se
                // la variabile bStop non è stata modificata
                // dall'evento aspettato
      } catch( InterruptedException ex) {}
    }
  }
  public synchronized void awakeThread() {
    System.out.println("AwakeThread");
    m_bStop = false;
    notify();      // a questo punto il thread si sveglia
  }
  public void run() {
      // do something
     m_bLoop = true;
     while (m_bLoop) { // pessima idea in casi pratici mettere loop
                       // infiniti, perché si rischia di non aver più
                       // controllo sul loop
       BlockingMethod();   // in questo modo metto il thread in attesa
                           // del prossimo evento notificato
       System.out.println("EventOccurred");
     }
  }
}
class SonThread extends Thread {
  AwakeEventListener m_awakeListener;
  public SonThread(AwakeEventListener listener) {
    m_awakeListener = listener;
  }
  public void run() {
    // do something until an event is generated
      m_awakeListener.awakeThread();  // awake the fatherThread
  }
}
Io ho semplificato l'esempio evitando di adoperare funzioni come
addAwakeListener e removeAwakeListener con relativa lista di
AwakeListener; Inoltre ho omesso di creare la funzione fireAwakeEvent
per la gestione dell'evento, delegando la chiamata di funzione
direttamente al metodo run, anche se normalmente non conviene far
così. 
Ciao, Max.

Grazie,
la soluzione più appropriata è la prima, ma ho ricontrato problemi nel
"cast" ? che mi indicavi di fare nei thread figli. Questo è il costruttore:
public listenproc(int LimInf, int LimSup, thrMain owner)
 {
  super();
  intLimInf=LimInf;
  intLimSup=LimSup;
  fndListener = (notifyListener) owner;
 }
owner gli viene passato dal thread padre in questo modo:
public class thrMain extends Thread
{
  public void run()
    {
      (new listenproc(0,16383,(thrMain)this)).start();
      (new listenproc(16384,32766,(thrMain)this)).start();
      (new listenproc(32767,49149,(thrMain)this)).start();
      (new listenproc(49150,65535,(thrMain)this)).start();
    }
}
Purtroppo perseguita a darmi errori.
Il mio intento erà quello di fare un certo lavoro (run dei figli) e di
poterlo visualizzare all'utente tramite progres bar (già creata) gestita dal
thread padre:
quest'ultimo al verificarsi dell'evento x disegnava una barra.
L'errore è un eccezione "cast..." sulla riga
" fndListener = (notifyListener) owner; "
Mi puoi aiutare?
Grazie tanto
Saman


è dovuto al fatto che tu passi "owner" come oggetto di tipo
"notifyListener" senza che l'oggetto "owner" implementi realmente tale
interfaccia: in Java (e non solo) puoi usare l'operatore di downcast
solo per convertire il tuo oggetto in un sottotipo che può essere o
una classe da cui deriva il tuo presente oggetto o un interfaccia che
è stata implementata sempre dal tuo oggetto.
Es.:
 class B extends A implements I {}
 class C extends B implements J,K {}
Per un oggetto di classe C ( =>  C c = new C();  )  diventano leciti
tali downcast:
 B b = (B) c;
 A b = (A) c;
 I b = (I) c;
 J b = (J) c;
 K b = (K) c;
Perché il compilatore compili senza dare errore basta aggiungere le
modifiche sopra riportate (cioé creare un'interfaccia "notifyListener"
di cui OGNI metodo troverà la sua definizione nella classe "thrMain",
e aggiungere la clausola "implements notifyListener" nella
dichiarazione della classe "thrMain").
Ovviamente poi ti toccherà anche dare una implementazione
significativa a ciascun metodo dell'interfaccia implementata da
"thrMain" e aggiungere dove servono delle chiamate a tali metodi ( del
tipo fndListener.doSomething() ) nella classe "listenproc", altrimenti
tutto il resto non serve a niente.
Ma certe cose sono più facili a farsi che a dirsi....
Presto imparerai che in Java iniziare i nomi delle classi con lettere
in maiuscolo e i nomi dei metodi e/o variabili (queste ultime non
costanti) con lettere in minuscolo è uno stile di programmazione ormai
troppo radicato per venirne meno (rischi di minare la leggibilità del
tuo codice).
Ciao, Max.