attendez que tous les threads aient terminé leur travail en java


91

J'écris une application qui a 5 threads qui obtiennent des informations du Web simultanément et remplissent 5 champs différents dans une classe de tampon.
J'ai besoin de valider les données du tampon et de les stocker dans une base de données lorsque tous les threads ont terminé leur travail.
Comment puis-je faire cela (être alerté lorsque tous les threads ont terminé leur travail)?


4
Thread.join est un moyen très idiosynchratique Java de bas niveau pour résoudre le problème. De plus, c'est problématique car l' API Thread est défectueuse: vous ne pouvez pas savoir si la jointure s'est terminée avec succès ou non (voir Java Concurrency en pratique ). Une abstraction de niveau supérieur, comme l'utilisation d'un CountDownLatch, peut être préférable et semblera plus naturelle aux programmeurs qui ne sont pas «coincés» dans l'état d'esprit Java-idiosynchratique. Ne discutez pas avec moi, allez discuter avec Doug Lea; )
Cedric Martin

Réponses:


119

L'approche que j'adopte consiste à utiliser un ExecutorService pour gérer des pools de threads.

ExecutorService es = Executors.newCachedThreadPool();
for(int i=0;i<5;i++)
    es.execute(new Runnable() { /*  your task */ });
es.shutdown();
boolean finished = es.awaitTermination(1, TimeUnit.MINUTES);
// all tasks have finished or the time has been reached.

7
@Leonid c'est exactement ce que fait shutdown ().
Peter Lawrey

3
while(!es.awaitTermination(1, TimeUnit.MINUTES));
Aquarius Power

3
@AquariusPower Vous pouvez simplement lui dire d'attendre plus longtemps ou pour toujours.
Peter Lawrey

1
Oh je comprends; j'ai donc ajouté un message dans la boucle disant qu'il attend que tous les threads se terminent; Merci!
Aquarius Power

1
@PeterLawrey, est-il nécessaire d'appeler es.shutdown();? que se passe-t-il si j'écris un code dans lequel j'exécute un thread en utilisant es.execute(runnableObj_ZipMaking);in tryblock et in finallyI called boolean finshed = es.awaitTermination(10, TimeUnit.MINUTES);. Donc, je suppose que cela devrait attendre que tous les threads aient terminé leur travail ou que le délai d'attente se produise (quel que soit le premier), mon hypothèse est-elle correcte? ou appeler shutdown()est obligatoire?
Amogh

52

Vous pouvez joinles fils. La jointure se bloque jusqu'à ce que le thread se termine.

for (Thread thread : threads) {
    thread.join();
}

Notez que joinjette un InterruptedException. Vous devrez décider quoi faire si cela se produit (par exemple, essayez d'annuler les autres threads pour éviter un travail inutile).


1
Ces threads s'exécutent-ils en parallèle ou séquentiellement les uns par rapport aux autres?
James Webster

5
@JamesWebster: Parallèle.
RYN

4
@James Webster: L'instruction t.join();signifie que le thread actuel se bloque jusqu'à ce que le thread se ttermine. Cela n'affecte pas le fil t.
Mark Byers

1
Merci. =] J'ai étudié le paralellisme à l'université, mais c'est la seule chose que j'ai eu du mal à apprendre! Heureusement, je n'ai pas à l'utiliser beaucoup maintenant ou quand je le fais, ce n'est pas trop complexe ou il n'y a pas de ressources partagées et le blocage n'est pas critique
James Webster

1
@ 4r1y4n Le fait que le code fourni soit vraiment parallèle dépend de ce que vous essayez d'en faire, et est davantage lié à l'agrégation de données réparties dans les collections à l'aide de threads joints. Vous rejoignez des threads, ce qui signifie potentiellement "joindre" des données. De plus, le parallélisme ne signifie PAS nécessairement la concurrence. Cela dépend des processeurs. Il se peut très bien que les threads fonctionnent en parallèle, mais les calculs se déroulent dans n'importe quel ordre déterminé par le processeur sous-jacent.

22

Jetez un œil à diverses solutions.

  1. join()L'API a été introduite dans les premières versions de Java. Certaines bonnes alternatives sont disponibles avec ce package simultané depuis la version JDK 1.5.

  2. 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é.

    Reportez-vous à cette question SE associée pour un exemple de code:

    Comment utiliser invokeAll () pour laisser tous les pools de threads faire leur tâche?

  3. 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.

    Un CountDownLatch est initialisé avec un nombre 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 .

    Reportez-vous à cette question pour l'utilisation de CountDownLatch

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

  4. ForkJoinPool ou newWorkStealingPool () dans les exécuteurs

  5. Parcourez tous les objets Future créés après avoir été soumis àExecutorService


11

Mis à part Thread.join()suggéré par d'autres, java 5 a introduit le framework exécuteur. Là, vous ne travaillez pas avec des Threadobjets. Au lieu de cela, vous soumettez vos objets Callableou Runnableà un exécuteur testamentaire. Il existe un exécuteur spécial destiné à exécuter plusieurs tâches et à renvoyer leurs résultats dans le désordre. C'est ça ExecutorCompletionService:

ExecutorCompletionService executor;
for (..) {
    executor.submit(Executors.callable(yourRunnable));
}

Ensuite, vous pouvez appeler à plusieurs reprises take()jusqu'à ce qu'il n'y ait plus d' Future<?>objets à renvoyer, ce qui signifie qu'ils sont tous terminés.


Une autre chose qui peut être pertinente, selon votre scénario est CyclicBarrier.

Une aide à la synchronisation qui permet à un ensemble de threads d'attendre l'un l'autre pour atteindre un point de barrière commun. Les CyclicBarriers sont utiles dans les programmes impliquant un groupe de threads de taille fixe qui doivent parfois attendre les uns les autres. La barrière est appelée cyclique car elle peut être réutilisée après la libération des threads en attente.


C'est proche, mais je ferais encore quelques ajustements. executor.submitrenvoie un Future<?>. J'ajouterais ces futurs à une liste, puis je bouclerais la liste appelant getchaque avenir.
Ray

En outre, vous pouvez instancier un constructeur en utilisant Executors, par exemple, Executors.newCachedThreadPool(ou similaire)
Ray

10

Une autre possibilité est l' CountDownLatchobjet, qui est utile pour des situations simples: puisque vous connaissez à l'avance le nombre de threads, vous l'initialisez avec le décompte pertinent, et passez la référence de l'objet à chaque thread.
À la fin de sa tâche, chaque thread appelle CountDownLatch.countDown()ce qui décrémente le compteur interne. Le thread principal, après avoir démarré tous les autres, doit faire l' CountDownLatch.await()appel de blocage. Il sera libéré dès que le compteur interne aura atteint 0.

Faites attention qu'avec cet objet, un InterruptedExceptionpeut également être lancé.


8

Tu fais

for (Thread t : new Thread[] { th1, th2, th3, th4, th5 })
    t.join()

Après cette boucle for, vous pouvez être sûr que tous les threads ont terminé leur travail.


6

Attendez / bloquez le fil principal jusqu'à ce que d'autres threads aient terminé leur travail.

Comme @Ravindra babudit, cela peut être réalisé de différentes manières, mais en montrant des exemples.

  • java.lang.Thread. join () Depuis: 1.0

    public static void joiningThreads() throws InterruptedException {
        Thread t1 = new Thread( new LatchTask(1, null), "T1" );
        Thread t2 = new Thread( new LatchTask(7, null), "T2" );
        Thread t3 = new Thread( new LatchTask(5, null), "T3" );
        Thread t4 = new Thread( new LatchTask(2, null), "T4" );
    
        // Start all the threads
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    
        // Wait till all threads completes
        t1.join();
        t2.join();
        t3.join();
        t4.join();
    }
    
  • java.util.concurrent.CountDownLatch depuis: 1.5

    • .countDown() «Décrémente le nombre du groupe de verrouillage.
    • .await() «Les méthodes d'attente se bloquent jusqu'à ce que le décompte actuel atteigne zéro.

    Si vous avez créé, latchGroupCount = 4alors countDown()devrait être appelé 4 fois pour faire le compte 0. Donc, cela await()libérera les threads de blocage.

    public static void latchThreads() throws InterruptedException {
        int latchGroupCount = 4;
        CountDownLatch latch = new CountDownLatch(latchGroupCount);
        Thread t1 = new Thread( new LatchTask(1, latch), "T1" );
        Thread t2 = new Thread( new LatchTask(7, latch), "T2" );
        Thread t3 = new Thread( new LatchTask(5, latch), "T3" );
        Thread t4 = new Thread( new LatchTask(2, latch), "T4" );
    
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    
        //latch.countDown();
    
        latch.await(); // block until latchGroupCount is 0.
    }
    

Exemple de code de classe Threaded LatchTask. Pour tester l'utilisation de l'approche joiningThreads(); et latchThreads();de la méthode principale.

class LatchTask extends Thread {
    CountDownLatch latch;
    int iterations = 10;
    public LatchTask(int iterations, CountDownLatch latch) {
        this.iterations = iterations;
        this.latch = latch;
    }

    @Override
    public void run() {
        String threadName = Thread.currentThread().getName();
        System.out.println(threadName + " : Started Task...");

        for (int i = 0; i < iterations; i++) {
            System.out.println(threadName + " : " + i);
            MainThread_Wait_TillWorkerThreadsComplete.sleep(1);
        }
        System.out.println(threadName + " : Completed Task");
        // countDown() « Decrements the count of the latch group.
        if(latch != null)
            latch.countDown();
    }
}
  • CyclicBarriers Une aide à la synchronisation qui permet à un ensemble de threads de s'attendre tous les uns les autres pour atteindre un point de barrière commun. Les CyclicBarriers sont utiles dans les programmes impliquant une partie de threads de taille fixe qui doivent parfois attendre les uns les autres. La barrière est appelée cyclique car elle peut être réutilisée après la libération des threads en attente.
    CyclicBarrier barrier = new CyclicBarrier(3);
    barrier.await();
    
    Par exemple, référez-vous à cette classe Concurrent_ParallelNotifyies .

  • Framework Executer: nous pouvons utiliser ExecutorService pour créer un pool de threads et suivre la progression des tâches asynchrones avec Future.

    • submit(Runnable), submit(Callable)qui renvoient Future Object. En utilisant la future.get()fonction, nous pouvons bloquer le thread principal jusqu'à ce que les threads de travail terminent leur travail.

    • invokeAll(...) - retourne une liste d'objets Future via laquelle vous pouvez obtenir les résultats des exécutions de chaque Callable.

Trouvez un exemple d'utilisation d'Interfaces Runnable, Callable with Executor Framework.


@Voir également


4

Stockez les objets Thread dans une collection (comme une liste ou un ensemble), puis parcourez la collection une fois que les threads sont démarrés et appelez join () sur les threads.



2

Bien que cela ne concerne pas le problème d'OP, si vous êtes intéressé par la synchronisation (plus précisément, un rendez-vous) avec exactement un thread, vous pouvez utiliser un échangeur

Dans mon cas, j'avais besoin de mettre en pause le thread parent jusqu'à ce que le thread enfant fasse quelque chose, par exemple acheva son initialisation. Un CountDownLatch fonctionne également bien.



1

essayez ceci, fonctionnera.

  Thread[] threads = new Thread[10];

  List<Thread> allThreads = new ArrayList<Thread>();

  for(Thread thread : threads){

        if(null != thread){

              if(thread.isAlive()){

                    allThreads.add(thread);

              }

        }

  }

  while(!allThreads.isEmpty()){

        Iterator<Thread> ite = allThreads.iterator();

        while(ite.hasNext()){

              Thread thread = ite.next();

              if(!thread.isAlive()){

                   ite.remove();
              }

        }

   }

1

J'ai eu un problème similaire et j'ai fini par utiliser Java 8 parallelStream.

requestList.parallelStream().forEach(req -> makeRequest(req));

C'est super simple et lisible. Dans les coulisses, il utilise le pool de jointure fork de la JVM par défaut, ce qui signifie qu'il attendra que tous les threads se terminent avant de continuer. Pour mon cas, c'était une solution intéressante, car c'était le seul parallelStream de mon application. Si vous avez plus d'un parallelStream en cours d'exécution simultanément, veuillez lire le lien ci-dessous.

Plus d'informations sur les flux parallèles ici .


0

Les réponses existantes ont dit pourrait join()chaque fil.

Mais il existe plusieurs façons d'obtenir le tableau / la liste des threads:

  • Ajoutez le fil dans une liste lors de la création.
  • Utilisez ThreadGrouppour gérer les threads.

Le code suivant utilisera l' ThreadGruopapproche. Il crée d'abord un groupe, puis lors de la création de chaque thread, spécifiez le groupe dans le constructeur, plus tard pourrait obtenir le tableau de thread viaThreadGroup.enumerate()


Code

SyncBlockLearn.java

import org.testng.Assert;
import org.testng.annotations.Test;

/**
 * synchronized block - learn,
 *
 * @author eric
 * @date Apr 20, 2015 1:37:11 PM
 */
public class SyncBlockLearn {
    private static final int TD_COUNT = 5; // thread count
    private static final int ROUND_PER_THREAD = 100; // round for each thread,
    private static final long INC_DELAY = 10; // delay of each increase,

    // sync block test,
    @Test
    public void syncBlockTest() throws InterruptedException {
        Counter ct = new Counter();
        ThreadGroup tg = new ThreadGroup("runner");

        for (int i = 0; i < TD_COUNT; i++) {
            new Thread(tg, ct, "t-" + i).start();
        }

        Thread[] tArr = new Thread[TD_COUNT];
        tg.enumerate(tArr); // get threads,

        // wait all runner to finish,
        for (Thread t : tArr) {
            t.join();
        }

        System.out.printf("\nfinal count: %d\n", ct.getCount());
        Assert.assertEquals(ct.getCount(), TD_COUNT * ROUND_PER_THREAD);
    }

    static class Counter implements Runnable {
        private final Object lkOn = new Object(); // the object to lock on,
        private int count = 0;

        @Override
        public void run() {
            System.out.printf("[%s] begin\n", Thread.currentThread().getName());

            for (int i = 0; i < ROUND_PER_THREAD; i++) {
                synchronized (lkOn) {
                    System.out.printf("[%s] [%d] inc to: %d\n", Thread.currentThread().getName(), i, ++count);
                }
                try {
                    Thread.sleep(INC_DELAY); // wait a while,
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            System.out.printf("[%s] end\n", Thread.currentThread().getName());
        }

        public int getCount() {
            return count;
        }
    }
}

Le thread principal attendra que tous les threads du groupe se terminent.


0

J'ai créé une petite méthode d'aide pour attendre la fin de quelques threads:

public static void waitForThreadsToFinish(Thread... threads) {
        try {
            for (Thread thread : threads) {
                thread.join();
            }
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

-1

Utilisez ceci dans votre thread principal: while (! Executor.isTerminated ()); Mettez cette ligne de code après avoir démarré tous les threads du service exécuteur. Cela ne démarrera le thread principal qu'une fois tous les threads démarrés par les exécuteurs terminés. Assurez-vous d'appeler executor.shutdown (); avant la boucle ci-dessus.


Il s'agit d'une attente active, ce qui obligera le CPU à exécuter en permanence une boucle vide. Très gaspilleur.
Adam Michalik
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.