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

126

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

Comment recevoir une notification lorsque chaque thread a fini de s'exécuter?

Existe-t-il un moyen de savoir si l'un (ou la totalité) du thread est terminé ou est toujours en cours d'exécution?

Ricardo Felgueiras
la source
1
Jetez un œil à la classe
Fortyrunner

Réponses:

228

Il existe plusieurs façons de procéder:

  1. Utilisez Thread.join () dans votre thread principal pour attendre de manière bloquante que chaque thread se termine, ou
  2. Vérifiez Thread.isAlive () dans un mode de sondage - généralement déconseillé - pour attendre que chaque thread soit terminé, ou
  3. Peu orthodoxe, pour chaque Thread en question, appelez setUncaughtExceptionHandler pour appeler une méthode dans votre objet et programmez chaque Thread pour lancer une exception non interceptée à la fin, ou
  4. Utilisez des verrous, des synchroniseurs ou des mécanismes de java.util.concurrent , ou
  5. Plus orthodoxe, créez un écouteur dans votre thread principal, puis programmez chacun de vos threads pour indiquer à l'auditeur qu'ils ont terminé.

Comment mettre en œuvre l'idée n ° 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<ThreadCompleteListener> listeners
                   = new CopyOnWriteArraySet<ThreadCompleteListener>();
  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();
}

puis chacun de vos threads s'étendra NotifyingThreadet au lieu de l'implémenter, run()il sera implémenté doRun(). Ainsi, une fois terminés, ils avertiront automatiquement toute personne en attente de notification.

Enfin, dans votre classe principale - celle qui démarre tous les Threads (ou au moins l'objet en attente de notification) - modifiez cette classe implement ThreadCompleteListeneret immédiatement après la création de chaque Thread s'ajoute à 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 notifyOfThreadCompleteméthode sera appelée avec l'instance Thread qui vient de se terminer (ou qui s'est plantée).

Notez que mieux vaut implements Runnableplutôt que extends Threadpour NotifyingThreadcar l'extension de Thread est généralement déconseillée dans le nouveau code. Mais je codifie votre question. Si vous modifiez la NotifyingThreadclasse à implémenter, Runnablevous devez modifier une partie de votre code qui gère les threads, ce qui est assez simple à faire.

Eddie
la source
Salut!! J'aime la dernière idée. Je dois implémenter un auditeur pour faire ça? Merci
Ricardo Felgueiras
4
mais en utilisant cette approche, notifiyListeners est appelé à l'intérieur de run () donc il sera appelé insisde le thread et les autres appels seront effectués là aussi, n'est-ce pas ainsi?
Jordi Puigdellívol
1
@Eddie Jordi demandait s'il est possible d'appeler la notifyméthode non pas dans l'indication de la runméthode, mais après.
Tomasz Dzięcielewski
1
La question est vraiment: comment désactiver maintenant le fil secondaire. Je sais que c'est terminé, mais comment puis-je accéder au fil principal maintenant?
Patrick
1
Ce fil est-il sûr? Il semble que notifyListeners (et donc notifyOfThreadComplete) sera appelé dans le NotifyingThread, plutôt que dans le thread qui a créé l'écouteur lui-même.
Aaron
13

Solution utilisant CyclicBarrier

public class Downloader {
  private CyclicBarrier barrier;
  private final static int NUMBER_OF_DOWNLOADING_THREADS;

  private DownloadingThread extends Thread {
    private final String url;
    public DownloadingThread(String 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://www.flickr.com/someUser/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 en cours d'exécution plus un pour le thread principal d'exécution (dans lequel startDownload () est en cours d'exécution)

label 1 - n-ième Téléchargement Le fil entre dans la salle d'attente

étiquette 3 - NUMBER_OF_DOWNLOADING_THREADS sont entrés dans la salle d'attente. Le fil principal de l'exécution les libère pour commencer à effectuer leurs tâches de téléchargement plus ou moins en même temps

étiquette 4 - le fil principal d'exécution entre dans la salle d'attente. C'est la partie du code la plus «délicate» à comprendre. Peu importe le fil qui entrera dans la salle d'attente pour la deuxième fois. Il est important que le thread entrant en dernier dans la salle s'assure 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 travail de téléchargement et entre dans la salle d'attente. Si c'est le dernier, c'est-à-dire que NUMBER_OF_DOWNLOADING_THREADS y sont déjà entrés, y compris le thread principal d'exécution, le thread principal ne continuera son exécution que lorsque tous les autres threads auront terminé le téléchargement.

Boris Pavlović
la source
9

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 assumez la responsabilité d'utiliser directement Threads, vous devriez probablement l'utiliser join()pour savoir quand un thread est terminé. Voici un mécanisme de rappel super simple. Commencez par étendre l' Runnableinterface pour avoir un rappel:

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

Ensuite, créez un exécuteur qui exécutera votre exécutable et vous rappellera lorsque cela sera fait.

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 à ajouter à votre CallbackRunnableinterface est un moyen de gérer toutes les exceptions, alors peut-être y mettre une public void uncaughtException(Throwable e);ligne et dans votre exécuteur, installer un Thread.UncaughtExceptionHandler pour vous envoyer à cette méthode d'interface.

Mais faire tout cela commence vraiment à sentir java.util.concurrent.Callable. Vous devriez vraiment envisager d'utiliser java.util.concurrentsi votre projet le permet.

broc.seib
la source
Je ne runner.join()sais pas trop ce que vous gagnez de ce mécanisme de rappel par rapport à un simple appel , puis au code que vous voulez après cela, puisque vous savez que le fil est terminé. Est-ce juste que vous définissez ce code comme une propriété de l'exécutable, de sorte que vous pourriez avoir des choses différentes pour différents exécutables?
Stephen
2
Oui, runner.join()c'est le moyen le plus direct d'attendre. Je supposais que l'OP ne voulait pas bloquer leur fil d'appel principal car ils demandaient à être "notifiés" pour chaque téléchargement, ce qui pouvait se terminer dans n'importe quel ordre. Cela offrait un moyen d'être notifié de manière asynchrone.
broc.seib
4

Voulez-vous attendre la fin? Si tel est le cas, utilisez la méthode Join.

Il existe également la propriété isAlive si vous souhaitez simplement la vérifier.

Jonathan Allen
la source
3
Notez que isAlive retourne false si le thread n'a pas encore commencé à s'exécuter (même si votre propre thread a déjà appelé start dessus).
Tom Hawtin - Tacle du
@ TomHawtin-tackline en êtes-vous sûr? Cela contredirait la documentation Java ("Un thread est vivant s'il a été démarré et n'est pas encore mort" - docs.oracle.com/javase/6/docs/api/java/lang/… ). Cela contredirait également les réponses ici ( stackoverflow.com/questions/17293304/… )
Stephen
@Stephen Il y a longtemps que j'ai écrit ça, mais ça semble être vrai. J'imagine que cela a causé à d'autres personnes des problèmes qui étaient frais dans ma mémoire il y a neuf ans. Ce qui est observable dépendra de la mise en œuvre. Vous dites à un Threadà start, ce que fait le thread, mais l'appel revient immédiatement. isAlivedevrait être un simple test de drapeau, mais quand je l'ai googlé, la méthode était native.
Tom Hawtin - tackline
4

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 fil maître qui attend que les 3 enfants se terminent, le maître continuerait alors l'exécution lorsque les 3 autres auraient terminé.

Miquel
la source
Attendre la sortie des 3 enfants peut ne pas correspondre au paradigme d'utilisation. S'il s'agit d'un gestionnaire de téléchargement, ils peuvent souhaiter démarrer 15 téléchargements et simplement supprimer l'état de la barre d'état ou alerter l'utilisateur lorsqu'un téléchargement est terminé, auquel cas un rappel fonctionnerait mieux.
digitaljoel
3

Vous pouvez également utiliser l' Executorsobjet pour créer un pool de threads ExecutorService . Ensuite, utilisez la invokeAllméthode pour exécuter chacun de vos threads et récupérer Futures. Cela bloquera jusqu'à ce que tous aient terminé l'exécution. Votre autre option serait d'exécuter chacun d'eux en utilisant le pool, puis d'appeler awaitTerminationpour bloquer jusqu'à ce que le pool ait fini de s'exécuter. Assurez-vous simplement d'appeler shutdown() lorsque vous avez terminé d'ajouter des tâches.

J Gitter
la source
2

Beaucoup de choses ont changé ces 6 dernières années sur le front du multi-threading.

Au lieu d'utiliser join()et de verrouiller l'API, vous pouvez utiliser

1. API ExecutorService invokeAll()

Exécute les tâches données, renvoyant une liste de Futures conservant leur statut et leurs résultats lorsque tout est terminé.

2. CountDownLatch

Aide à la synchronisation qui permet à un ou plusieurs threads d'attendre la fin d'un ensemble d'opérations en cours d'exécution dans d'autres threads.

A CountDownLatchest initialisé avec un décompte donné. Les méthodes d'attente se bloquent jusqu'à ce que le nombre actuel atteigne zéro en raison des appels de la countDown()méthode, après quoi tous les threads en attente sont libérés et toutes les invocations ultérieures de wait sont immédiatement renvoyées. Il s'agit d'un phénomène ponctuel - le décompte ne peut pas être réinitialisé. Si vous avez besoin d'une version qui réinitialise le nombre, envisagez d'utiliser un CyclicBarrier.

3. ForkJoinPool ou newWorkStealingPool()dans Executors est une autre manière

4.Itérer à travers toutes les Futuretâches à partir de la soumission ExecutorServiceet vérifier l'état avec blocage de l'appel get()sur l' Futureobjet

Jetez un œil aux questions SE connexes:

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

Executors: Comment attendre de manière synchrone la fin de toutes les tâches si les tâches sont créées de manière récursive?

Ravindra babu
la source
2

Je suggérerais de regarder le javadoc pour la classe Thread .

Vous disposez de plusieurs mécanismes de manipulation des threads.

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

  • Interrogez régulièrement l'état du thread des threads générés.

  • Mettez tous les threads générés dans un autre ThreadGroupet interrogez le activeCount()sur le ThreadGroupet attendez qu'il atteigne 0.

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

Je suis sûr qu'il me manque encore de nombreuses autres façons.

digitaljoel
la source
1

Voici une solution simple, courte, facile à comprendre et qui fonctionne parfaitement pour moi. J'avais besoin de dessiner sur l'écran lorsqu'un autre fil se terminait; mais ne pouvait pas 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 sur true à la fin. Cela est récupéré dans le thread principal par 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 l'on vérifie l'autre thread une fois par seconde. Lorsque l'autre thread se termine, cela peut commencer 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, démarrez le tout en cours d'exécution quelque part dans votre code en appelant:

void startThread()
{
   myThread();
   checkThread();
}
DreamMaster Pro
la source
1

Je suppose que le moyen le plus simple est d'utiliser la ThreadPoolExecutorclasse.

  1. Il a une file d'attente et vous pouvez définir le nombre de threads qui doivent fonctionner en parallèle.
  2. Il a de belles méthodes de rappel:

Méthodes de crochet

Cette classe fournit des méthodes beforeExecute(java.lang.Thread, java.lang.Runnable)et des afterExecute(java.lang.Runnable, java.lang.Throwable)méthodes remplaçables protégées qui sont appelées avant et après l'exécution de chaque tâche. Ceux-ci peuvent être utilisés pour manipuler l'environnement d'exécution; par exemple, réinitialiser ThreadLocals, collecter des statistiques ou ajouter des entrées de journal. En outre, la méthode terminated()peut être remplacée pour effectuer tout traitement spécial qui doit être effectué une fois que l'exécuteur est complètement terminé.

c'est exactement ce dont nous avons besoin. Nous remplacerons afterExecute()pour obtenir des rappels une fois que chaque thread est terminé et nous remplacerons terminated()pour savoir quand tous les threads sont terminés.

Alors voici ce que tu devrais faire

  1. Créez un exécuteur testamentaire:

    private ThreadPoolExecutor executor;
    private int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();    
    
    
    
    private void initExecutor() {
    
    executor = new ThreadPoolExecutor(
            NUMBER_OF_CORES * 2,  //core pool size
            NUMBER_OF_CORES * 2, //max pool size
            60L, //keep aive time
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<Runnable>()
    ) {
    
        @Override
        protected void afterExecute(Runnable r, Throwable t) {
            super.afterExecute(r, t);
                //Yet another thread is finished:
                informUiAboutProgress(executor.getCompletedTaskCount(), listOfUrisToProcess.size());
            }
        }
    
    };
    
        @Override
        protected void terminated() {
            super.terminated();
            informUiThatWeAreDone();
        }
    
    }
  2. Et commencez vos discussions:

    private void startTheWork(){
        for (Uri uri : listOfUrisToProcess) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    doSomeHeavyWork(uri);
                }
            });
        }
        executor.shutdown(); //call it when you won't add jobs anymore 
    }

La méthode Inside informUiThatWeAreDone();fait tout ce que vous devez faire lorsque tous les threads sont terminés, par exemple, mettez à jour l'interface utilisateur.

NOTE: N'oubliez pas d'utiliser des synchronizedméthodes puisque vous faites votre travail en parallèle et soyez TRÈS PRUDENT si vous décidez d'appeler une synchronizedméthode à partir d'une autre synchronizedméthode! Cela conduit souvent à des blocages

J'espère que cela t'aides!

Kirill Karmazin
la source
0

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

Akarnokd
la source
0

Consultez la documentation Java de la classe Thread. Vous pouvez vérifier l'état du thread. Si vous placez les trois threads dans des variables membres, les trois threads peuvent lire les états de chacun.

Vous devez être un peu prudent, cependant, car vous pouvez provoquer des conditions de concurrence 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 dans les mêmes variables.

Don Kirkby
la source