web-dev-qa-db-fra.com

Comment savoir si d'autres discussions sont terminées?

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

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

Y at-il un moyen de savoir si un (ou tous) du thread est terminé ou est en cours d'exécution?

120
Ricardo Felgueiras

Vous pouvez le faire de différentes manières:

  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 () de manière sélective - généralement déconseillée - d'attendre la fin de chaque thread, ou
  3. Non orthodoxe, pour chaque thread en question, appelez setUncaughtExceptionHandler pour appeler une méthode dans votre objet et programmez chaque thread pour qu'il lève une exception non capturée à la fin, ou
  4. Utilisez des verrous, synchroniseurs ou 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 mettre en œuvre l'idée # 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();
}

et ensuite chacun de vos threads s'étendra NotifyingThread et au lieu d'implémenter run(), il implémentera doRun(). Ainsi, quand ils auront terminé, ils informeront automatiquement toute personne en attente de notification.

Enfin, dans votre classe principale - celle qui démarre tous les threads (ou du moins l'objet en attente de notification) - modifiez cette classe en implement ThreadCompleteListener Et 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

ensuite, à la fin de chaque thread, votre méthode notifyOfThreadComplete sera invoquée avec l'instance de thread qui vient de se terminer (ou s'est écrasée).

Notez qu'il serait préférable d'utiliser implements Runnable Plutôt que extends Thread Pour NotifyingThread, car l'extension du thread est généralement déconseillée dans le nouveau code. Mais je code à votre question. Si vous changez la classe NotifyingThread pour implémenter Runnable, vous devez alors modifier une partie du code qui gère les threads, ce qui est assez simple à faire.

223
Eddie

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 - Une barrière cyclique est créée avec un nombre de partis é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 - Le n-ième DownloadingThread entre dans la salle d'attente

label 3 - NUMBER_OF_DOWNLOADING_THREADS est entré dans la salle d'attente. Le thread principal d'exécution les libère pour qu'ils puissent commencer à télécharger leurs tâches plus ou moins en même temps

label 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 fil entré dans la salle 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 travail de téléchargement et entre dans la salle d'attente. S'il s'agit du dernier, c'est-à-dire que NUMBER_OF_DOWNLOADING_THREADS l'a déjà entrée, y compris le fil d'exécution principal, le thread principal continuera son exécution uniquement lorsque le téléchargement de tous les autres threads sera terminé.

13
Boris Pavlović

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

Si vous n'utilisez pas Java.util.concurrent.* Et prenez la responsabilité d'utiliser directement les threads, vous devriez probablement utiliser join() pour savoir quand un thread est terminé. Voici un mécanisme de rappel super simple. D'abord, étendez l'interface Runnable pour avoir un rappel:

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

Ensuite, faites un exécuteur qui exécutera votre runnable et vous rappellera quand ce 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 solution évidente à ajouter à votre interface CallbackRunnable est un moyen de gérer les exceptions. Vous pouvez donc éventuellement insérer une ligne public void uncaughtException(Throwable e); et installer un Thread.UncaughtExceptionHandler dans votre exécuteur. pour vous envoyer à cette méthode d'interface.

Mais faire tout ce qui commence vraiment à sentir comme Java.util.concurrent.Callable. Vous devriez vraiment envisager d'utiliser Java.util.concurrent Si votre projet le permet.

8
broc.seib

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.

4
Jonathan Allen

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 soient terminés, le maître continuerait alors l'exécution une fois les 3 autres terminés.

4
Miquel

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

Vous avez plusieurs mécanismes pour manipuler les threads.

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

  • Interrogez l'état des threads à intervalles réguliers.

  • Placez tous les threads générés dans un ThreadGroup séparé et interrogez la activeCount() sur le ThreadGroup et attendez qu'il soit mis à 0.

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

Je suis sûr qu'il y a plein d'autres manières qui me manquent.

2
digitaljoel

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 de verrouiller l'API, vous pouvez utiliser

1 . ExecutorServiceinvokeAll() API

Exécute les tâches données, en retournant une liste des contrats à terme contenant leur statut et leurs résultats une fois tous complétés.

2 . CountDownLatch

Une aide à la synchronisation qui permet à un ou plusieurs threads d'attendre la fin d'un ensemble d'opérations effectuées dans d'autres threads.

Un CountDownLatch est initialisé avec un nombre donné. Les méthodes en attente bloquent jusqu'à ce que le nombre actuel atteigne zéro en raison d'appels de la méthode countDown(), après quoi tous les threads en attente sont libérés et tous les appels ultérieurs de wait retournent immédiatement. C'est un phénomène ponctuel - le 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() in Executors est un autre moyen

4.Utilisez toutes les Future tâches de submit sur ExecutorService et vérifiez l'état avec l'appel bloquant get() sur Future objet

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

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

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

1
Ravindra babu

Vous pouvez également utiliser l'objet 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 les Futures. Cela bloquera jusqu'à ce que tous aient terminé l'exécution. Votre autre option serait d’exécuter chacune d’elles en utilisant le pool, puis d’appeler awaitTermination pour bloquer jusqu’à ce que le pool soit terminé. Veillez simplement à appeler shutdown () lorsque vous avez terminé d'ajouter des tâches.

1
J Gitter

Je suppose que le moyen le plus simple consiste à utiliser ThreadPoolExecutor class.

  1. Il a une file d'attente et vous pouvez définir combien de threads doivent travailler en parallèle.
  2. Il a Nice méthodes de rappel:

méthodes de crochet

Cette classe fournit les méthodes protégées beforeExecute(Java.lang.Thread, Java.lang.Runnable) et afterExecute(Java.lang.Runnable, Java.lang.Throwable) 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, rassembler des statistiques ou ajouter des entrées de journal. De plus, la méthode terminated() peut être remplacée pour effectuer tout traitement spécial à effectuer une fois que l'exécuteur est complètement terminé.

c'est exactement ce dont nous avons besoin. Nous allons écraser afterExecute() pour obtenir des rappels après chaque thread et écraser terminated() pour savoir quand tous les threads sont terminés.

Alors, voici ce que vous devriez faire

  1. Créer un exécuteur:

    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 
    }
    

Dans la méthode informUiThatWeAreDone();, faites 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 les méthodes synchronized puisque vous faites votre travail en parallèle et soyez très prudent si vous décidez d'appeler synchronized méthode à partir d'une autre méthode synchronized! Cela conduit souvent à des impasses

J'espère que cela t'aides!

1
Kirill Karmazin

Voici une solution simple, courte, facile à comprendre et qui fonctionne parfaitement pour moi. Je devais dessiner à l'écran quand un autre fil se termine; 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 repris dans le fil principal par la boucle "postDelayed", où il est répondu.

(2) Mon fil de discussion 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 contrôle l'autre thread une fois par seconde. Lorsque l'autre thread se termine, cela peut commencer comme nous le souhaitons.

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 dans votre code en appelant:

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

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 pourront alors se lire l’état de chacun.

Vous devez cependant être un peu prudent, car vous pouvez provoquer des conditions de concurrence critique entre les threads. Essayez simplement d'éviter une logique compliquée basée sur l'état des autres threads. Évitez certainement que plusieurs threads écrivent dans les mêmes variables.

0
Don Kirkby

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

0
akarnokd