2010-03-05 6 views
4

J'ai un thread (Runnable) qui démarre un certain nombre d'autres threads (Runnables). Lorsque chaque thread enfant se termine, il doit déclencher un événement (ou quelque chose de similaire) et renvoyer une notification au thread parent. Je ne peux pas voir d'événements en Java (ala C#) - j'espérais pouvoir simplement m'inscrire dans le parent de l'événement 'I'm finished' de l'objet enfant, mais il ne semble pas que je puisse le faire. Comment suggérez-vous que j'accomplisse cela?Quelle est la meilleure façon de réaliser ce comportement de thread/événement en Java?

Merci

+4

Vous pouvez vous joindre à un fil, mais qui bloque jusqu'à ce que le fil est fini. L'autre option, si vous pouvez faire en sorte que votre code utilise le framework Executor, est de faire vos autres tâches Runnables à la place. Ensuite, vous surchargez la méthode 'done' de la classe' FutureTask' (via une sous-classe que vous écrivez) pour signaler la fin de votre tâche. –

+2

@Chris Jester-Young: Cela devrait être une réponse, pas un commentaire. –

+1

@MalcomTucker Vous devriez utiliser un CoutnDownLatch à cette fin ... voir ma réponse pour plus de détails. – Kiril

Répondre

2

Vous pouvez utiliser une variante sur le Observer pattern. Implémentez une fonction de rappel (telle que void finished(SomeArgs args)) dans le parent et construisez chaque enfant avec une référence à son parent. Lorsque l'enfant a terminé, demandez-lui d'appeler la méthode finished() du parent.

Assurez-vous que le rappel est thread-safe!

3

Vous créez une interface sur votre objet parent

public interface EventListener { 
    void trigger(Object event); 
} 

public class Parent implements EventListener { 
    public synchronized void trigger(Object event) { 
     // process events. 
    } 
} 

public class Child implements Runnable { 
    private final EventListener listener; 

    public Child(EventListener listen) { 
     listener = listen; 
    } 

    public void run() { 
     //do stuff 
     listener.trigger(results); 
    } 
} 
+1

Notez la méthode de déclenchement synchronisé - c'est important. Et le reste de la classe parent doit être thread-safe par rapport à trigger() aussi. –

+1

Bien sûr, si le parent et l'enfant sont intimement liés, c'est exagéré; l'enfant pourrait juste connaître Parent directement et invoquer l'une de ses méthodes. –

+1

Un CountDownLatch est conçu spécifiquement pour la communication entre les threads: aucun verrouillage requis. – Kiril

0

Cela n'utilise pas les événements, mais est juste un des je suis sûr que beaucoup de façons d'y parvenir. Mise en garde rapide: pour que cela fonctionne, vous devez lancer votre Runnable dans un objet Thread, ou modifier votre interface pour avoir une sorte de méthode isStopped() sur votre Runnable, qui retournera si votre Runnable était toujours en cours d'exécution.

Vous pouvez faire en sorte que le thread parent conserve la trace de tous ses threads enfants dans une liste. Lorsqu'un thread enfant se termine, placez la valeur calculée dans un champ, disons, result, et créez une méthode appelée getResult().

Demandez au thread parent de parcourir régulièrement la liste et de vérifier si le thread s'est arrêté. Si vous lancez votre Runnable sur un objet Thread, il existe une méthode appelée isAlive() pour savoir si un thread s'est arrêté. Si c'est le cas, appelez getResult() et faites ce que vous voulez.

Dans le fil parent, vous pouvez le faire:

Boolean running = true; 
while (running) { 
    //iterate through list 
    //if stopped, get value and do whatever 
    //if all the child threads are stopped, stop this thread and do whatever 
    Thread.sleep(1000); //makes this parent thread pause for 1 second before stopping again 
} 
+1

Pourquoi interroger quand vous pouvez pousser l'information à la place? – danben

+0

C'est juste une alternative. –

+0

nah, certainement pas envie de faire cela, j'ai potentiellement des milliers de threads ... – MalcomTucker

4

Java a un CountDownLatch dans sa bibliothèque de threads. Créez un CountDownLatch et initialisez-le avec le nombre de threads que vous allez exécuter. Lorsque vous créez vos fils, vous devez leur donner le loquet et chaque fil le signalera quand il sera terminé. Votre thread principal bloquera jusqu'à ce que tous les threads de travail aient terminé.

Avec CountDownLatch vous obtiendrez une communication sans serrure avec vos fils.

directement à partir de la documentation de Java:

class Driver { // ... 
    void main() throws InterruptedException { 
    CountDownLatch startSignal = new CountDownLatch(1); 
    CountDownLatch doneSignal = new CountDownLatch(N); 

    for (int i = 0; i < N; ++i) // create and start threads 
     new Thread(new Worker(startSignal, doneSignal)).start(); 

    doSomethingElse();   // don't let run yet 
    startSignal.countDown();  // let all threads proceed 
    doSomethingElse(); 
    doneSignal.await();   // wait for all to finish 
    } 
} 

class Worker implements Runnable { 
    private final CountDownLatch startSignal; 
    private final CountDownLatch doneSignal; 
    Worker(CountDownLatch startSignal, CountDownLatch doneSignal) { 
     this.startSignal = startSignal; 
     this.doneSignal = doneSignal; 
    } 
    public void run() { 
     try { 
     startSignal.await(); 
     doWork(); 
     doneSignal.countDown(); 
     } catch (InterruptedException ex) {} // return; 
    } 

    void doWork() { ... } 
} 

http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/CountDownLatch.html

0

La classe java.util.concurrent.ThreadPoolExecutor fait ce que vous avez besoin. Il exécute un certain nombre de threads et fournit un hook qui est appelé après chaque Runnable est terminé. Basiquement vous pouvez créer une sous-classe annonymous et passer outre afterExecute.Comme ceci:

ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 20, 5, 
     TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(50)) { 
    @Override 
    protected void afterExecute(Runnable r, Throwable t) { 
     // do your callback stuff here 
    } 
}; 

Voici l'exemple complet:

import java.util.concurrent.ArrayBlockingQueue; 
import java.util.concurrent.ThreadPoolExecutor; 
import java.util.concurrent.TimeUnit; 

public class Main { 
    private static int ready = 0; 

    public static void main(String[] args) throws InterruptedException { 
     ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 20, 5, 
       TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(50)) { 
      @Override 
      protected void afterExecute(Runnable r, Throwable t) { 
       ready++; 
      } 
     }; 

     for (int n = 0; n < 5; n++) 
      executor.execute(createTask()); 
     executor.shutdown(); 

     while(ready < 5) { 
      System.out.println("Ready: " + ready); 
      Thread.sleep(100); 
     } 

     System.out.println("Ready with all."); 
    } 

    private static Runnable createTask() { 
     return new Runnable() { 
      @Override 
      public void run() { 
       try { 
        Thread.sleep((long) (Math.random() * 1000)); 
       } catch (InterruptedException e) { 
        // ignore exception to make debugging a little harder 
       } 
      } 
     }; 
    } 

} 

sortie est:

Ready: 0 
Ready: 1 
Ready: 1 
Ready: 3 
Ready: 3 
Ready: 4 
Ready: 4 
Ready: 4 
Ready: 4 
Ready with all. 
Questions connexes