Comment savoir si d’autres threads sont terminés?

J’ai un object avec une méthode nommée StartDownload() , qui démarre trois threads.

Comment puis-je recevoir une notification lorsque l’exécution de chaque thread est terminée?

Existe-t-il un moyen de savoir si un (ou tous les) threads est terminé ou s’exécute toujours?

Vous pouvez le faire de plusieurs manières:

  1. Utilisez Thread.join () dans votre thread principal pour attendre que chaque thread se termine ou
  2. Vérifiez Thread.isAlive () en mode d’interrogation – généralement déconseillé – pour attendre la fin de chaque thread ou
  3. Unorthodox, pour chaque thread en question, appelez setUncaughtExceptionHandler pour appeler une méthode dans votre object et programmez chaque thread pour générer une exception non capturée à la fin de l’opération, ou
  4. Utilisez des verrous ou des synchroniseurs ou des mécanismes de java.util.concurrent ou
  5. Plus orthodoxe, créez un auditeur dans votre thread principal, puis programmez chacun de vos threads pour indiquer à l’auditeur qu’ils ont terminé.

Comment implémenter Idea # 5? Eh bien, une façon est de créer d’abord une interface:

 public interface ThreadCompleteListener { void notifyOfThreadComplete(final Thread thread); } 

puis créez la classe suivante:

 public abstract class NotifyingThread extends Thread { private final Set listeners = new CopyOnWriteArraySet(); public final void addListener(final ThreadCompleteListener listener) { listeners.add(listener); } public final void removeListener(final ThreadCompleteListener listener) { listeners.remove(listener); } private final void notifyListeners() { for (ThreadCompleteListener listener : listeners) { listener.notifyOfThreadComplete(this); } } @Override public final void run() { try { doRun(); } finally { notifyListeners(); } } public abstract void doRun(); } 

et puis chacun de vos threads étendra NotifyingThread et au lieu d’implémenter run() il implémentera doRun() . Ainsi, lorsqu’ils seront terminés, ils avertiront automatiquement toute personne en attente de notification.

Enfin, dans votre classe principale – celle qui lance tous les threads (ou au moins l’object en attente de notification) – modifiez cette classe pour implement ThreadCompleteListener et, immédiatement après la création de chaque thread, s’ajoutez à la liste des écouteurs:

 NotifyingThread thread1 = new OneOfYourThreads(); thread1.addListener(this); // add ourselves as a listener thread1.start(); // Start the Thread 

puis, à la fin de chaque thread, votre méthode notifyOfThreadComplete sera appelée avec l’instance Thread qui vient de s’achever (ou de se bloquer).

Notez qu’il serait préférable d’ implements Runnable plutôt que d’ extends Thread pour NotifyingThread car l’extension de Thread est généralement déconseillée dans le nouveau code. Mais je code à votre question. Si vous modifiez la classe NotifyingThread pour implémenter Runnable vous devez modifier une partie de votre code qui gère Threads, ce qui est assez simple à faire.

Solution utilisant CyclicBarrier

 public class Downloader { private CyclicBarrier barrier; private final static int NUMBER_OF_DOWNLOADING_THREADS; private DownloadingThread extends Thread { private final Ssortingng url; public DownloadingThread(Ssortingng url) { super(); this.url = url; } @Override public void run() { barrier.await(); // label1 download(url); barrier.await(); // label2 } } public void startDownload() { // plus one for the main thread of execution barrier = new CyclicBarrier(NUMBER_OF_DOWNLOADING_THREADS + 1); // label0 for (int i = 0; i < NUMBER_OF_DOWNLOADING_THREADS; i++) { new DownloadingThread("http://soffr.miximages.com/java/pic   i   .jpg").start(); } barrier.await(); // label3 displayMessage("Please wait..."); barrier.await(); // label4 displayMessage("Finished"); } } 

label0 - la barrière cyclique est créée avec un nombre de parties égal au nombre de threads d'exécution plus un pour le thread d'exécution principal (dans lequel startDownload () est en cours d'exécution)

label 1 - n-ème DownloadingThread entre dans la salle d'attente

L'étiquette 3 - NUMBER_OF_DOWNLOADING_THREADS est entrée dans la salle d'attente. Le thread d'exécution principal les libère pour qu'ils commencent à télécharger leurs tâches plus ou moins au même moment

étiquette 4 - le fil d'exécution principal entre dans la salle d'attente. C'est la partie la plus délicate du code à comprendre. Peu importe le fil qui entrera dans la salle d'attente pour la deuxième fois. Il est important que le dernier thread entrant dans la pièce garantisse que tous les autres threads de téléchargement ont terminé leurs tâches de téléchargement.

label 2 - n-ème DownloadingThread a terminé son téléchargement et entre dans la salle d'attente. Si c'est le dernier c'est-à-dire déjà NUMBER_OF_DOWNLOADING_THREADS y est entré, y compris le thread d'exécution principal, le thread principal continuera son exécution seulement lorsque tous les autres threads auront fini de télécharger.

Vous devriez vraiment préférer une solution qui utilise java.util.concurrent . Trouvez et lisez Josh Bloch et / ou Brian Goetz sur le sujet.

Si vous n’utilisez pas java.util.concurrent.* Et que vous utilisez directement Threads, vous devriez probablement utiliser join() pour savoir quand un thread est terminé. Voici un mécanisme de rappel super simple. Runnable abord l’interface Runnable pour avoir un rappel:

 public interface CallbackRunnable extends Runnable { public void callback(); } 

Ensuite, créez un exécuteur exécutant votre exécutable et rappelez-le une fois terminé.

 public class CallbackExecutor implements Executor { @Override public void execute(final Runnable r) { final Thread runner = new Thread(r); runner.start(); if ( r instanceof CallbackRunnable ) { // create a thread to perform the callback Thread callerbacker = new Thread(new Runnable() { @Override public void run() { try { // block until the running thread is done runner.join(); ((CallbackRunnable)r).callback(); } catch ( InterruptedException e ) { // someone doesn't want us running. ok, maybe we give up. } } }); callerbacker.start(); } } } 

L’autre chose évidente à append à votre interface CallbackRunnable est un moyen de gérer toutes les exceptions, donc peut-être mettre une exception public void uncaughtException(Throwable e); ligne et dans votre exécuteur, installez un Thread.UncaughtExceptionHandler pour vous envoyer à cette méthode d’interface.

Mais faire tout ce qui commence vraiment à sentir java.util.concurrent.Callable . Vous devriez vraiment regarder java.util.concurrent si votre projet le permet.

Voulez-vous attendre qu’ils finissent? Si c’est le cas, utilisez la méthode Join.

Il y a aussi la propriété isAlive si vous voulez juste la vérifier.

Vous pouvez interroger l’instance de thread avec getState () qui renvoie une instance de l’énumération Thread.State avec l’une des valeurs suivantes:

 * NEW A thread that has not yet started is in this state. * RUNNABLE A thread executing in the Java virtual machine is in this state. * BLOCKED A thread that is blocked waiting for a monitor lock is in this state. * WAITING A thread that is waiting indefinitely for another thread to perform a particular action is in this state. * TIMED_WAITING A thread that is waiting for another thread to perform an action for up to a specified waiting time is in this state. * TERMINATED A thread that has exited is in this state. 

Cependant, je pense que ce serait une meilleure conception d’avoir un thread principal qui attend que les 3 enfants finissent, le maître continuerait alors l’exécution quand les 3 autres auront fini.

Je suggère de regarder le javadoc pour la classe Thread .

Vous avez plusieurs mécanismes pour la manipulation des threads.

  • Votre thread principal pourrait join() les trois threads en série et ne continuerait pas avant que les trois ne soient terminés.

  • Interrogez périodiquement l’état des threads générés.

  • Placez tous les threads générés dans un ThreadGroup distinct et interrogez activeCount() sur le ThreadGroup et attendez qu’il ThreadGroup 0.

  • Configurez un type d’interface de rappel ou d’écoute personnalisé pour la communication inter-thread.

Je suis sûr qu’il me manque encore d’autres moyens.

Vous pouvez également utiliser l’object Executors pour créer un pool de threads ExecutorService . Ensuite, utilisez la méthode invokeAll pour exécuter chacun de vos threads et récupérer Futures. Cela bloquera jusqu’à ce que l’exécution soit terminée. Votre autre option serait d’exécuter chacun en utilisant le pool, puis d’appeler awaitTermination pour bloquer jusqu’à la fin de l’exécution du pool. Veillez simplement à appeler shutdown () lorsque vous avez terminé d’append des tâches.

Beaucoup de choses ont été changées au cours des 6 dernières années sur le front multi-threading.

Au lieu d’utiliser join() et lock API, vous pouvez utiliser

1. ExecutorService invokeAll() API

Exécute les tâches données, en retournant une liste de contrats à terme conservant leur statut et leurs résultats une fois terminés.

2. CountDownLatch

Une aide à la synchronisation qui permet à un ou plusieurs threads d’attendre qu’un ensemble d’opérations soit exécuté dans d’autres threads.

Un CountDownLatch est initialisé avec un nombre donné. Les méthodes d’attente bloquent jusqu’à ce que le nombre actuel atteigne zéro en raison des countDown() méthode countDown() , après quoi tous les threads en attente sont libérés et toutes les invocations d’attente suivantes sont countDown() immédiatement. Ceci est un phénomène à un coup – le compte ne peut pas être réinitialisé. Si vous avez besoin d’une version qui réinitialise le compte, envisagez d’utiliser un CyclicBarrier.

3. ForkJoinPool ou newWorkStealingPool() dans les exécuteurs est une autre manière

4.Sélectionnez toutes les tâches Future de submit sur ExecutorService et vérifiez le statut avec un appel bloquant get() sur un object Future

Jetez un coup d’œil aux questions SE connexes:

Comment attendre un thread qui génère son propre thread?

Les exécuteurs: Comment attendre de manière synchrone jusqu’à ce que toutes les tâches soient terminées si les tâches sont créées de manière récursive?

Voici une solution simple, courte, facile à comprendre et qui me convient parfaitement. J’avais besoin de dessiner à l’écran lorsqu’un autre thread se termine; mais n’a pas pu parce que le thread principal a le contrôle de l’écran. Alors:

(1) J’ai créé la variable globale: boolean end1 = false; Le thread le définit comme vrai à la fin. Cela est pris en compte dans la boucle “postDelayed”, où il est répondu.

(2) Mon fil contient:

 void myThread() { end1 = false; new CountDownTimer(((60000, 1000) { // milliseconds for onFinish, onTick public void onFinish() { // do stuff here once at end of time. end1 = true; // signal that the thread has ended. } public void onTick(long millisUntilFinished) { // do stuff here repeatedly. } }.start(); } 

(3) Heureusement, “postDelayed” s’exécute dans le thread principal, c’est donc là que vous contrôlez l’autre thread une fois par seconde. Lorsque l’autre thread se termine, cela peut commencer tout ce que nous voulons faire ensuite.

 Handler h1 = new Handler(); private void checkThread() { h1.postDelayed(new Runnable() { public void run() { if (end1) // resond to the second thread ending here. else h1.postDelayed(this, 1000); } }, 1000); } 

(4) Enfin, lancez tout ce qui tourne quelque part dans votre code en appelant:

 void startThread() { myThread(); checkThread(); } 

Vous pouvez également utiliser SwingWorker, qui prend en charge les modifications de propriété intégrées. Voir addPropertyChangeListener () ou la méthode get () pour un exemple d’écouteur de changement d’état.

Regardez la documentation Java pour la classe Thread. Vous pouvez vérifier l’état du thread. Si vous placez les trois threads dans les variables membres, alors les trois threads peuvent lire les états des uns et des autres.

Vous devez être prudent, car vous pouvez créer des conditions de course entre les threads. Essayez simplement d’éviter une logique compliquée basée sur l’état des autres threads. Évitez définitivement l’écriture de plusieurs threads sur les mêmes variables.