Come attendere il completamento di un numero di thread?


109

Qual è un modo per aspettare semplicemente che tutto il processo threaded finisca? Ad esempio, diciamo che ho:

public class DoSomethingInAThread implements Runnable{

    public static void main(String[] args) {
        for (int n=0; n<1000; n++) {
            Thread t = new Thread(new DoSomethingInAThread());
            t.start();
        }
        // wait for all threads' run() methods to complete before continuing
    }

    public void run() {
        // do something here
    }


}

Come posso modificare questo in modo che il main()metodo si fermi al commento finché tutti i run()metodi dei thread non escono? Grazie!

Risposte:


163

Metti tutti i thread in un array, li avvii tutti e quindi hai un ciclo

for(i = 0; i < threads.length; i++)
  threads[i].join();

Ogni join si bloccherà fino al completamento del rispettivo thread. I thread possono essere completati in un ordine diverso da quello in cui li unisci, ma non è un problema: quando il ciclo termina, tutti i thread sono completati.


1
@ Mykola: qual è esattamente il vantaggio di utilizzare un gruppo di thread? Solo perché l'API è presente non significa che devi usarla ...
Martin v. Löwis

2
Vedere: "Un gruppo di thread rappresenta un insieme di thread." Questo è semantico corretto per questo caso d'uso! E: "Un thread può accedere alle informazioni sul proprio gruppo di thread"
Martin K.

4
Il libro "Effective Java" consiglia di evitare i gruppi di thread (elemento 73).
Bastien Léonard

2
I bug menzionati in Java efficace avrebbero dovuto essere corretti in Java 6. Se le versioni java più recenti non sono una restrizione, è meglio usare Futures per risolvere i problemi dei thread. Martin v. Löwis: Hai ragione. Non è rilevante per questo problema, ma è bello ottenere più informazioni sui thread in esecuzione da un oggetto (come ExecutorService). Penso che sia bello usare determinate funzionalità per risolvere un problema; forse avrai bisogno di maggiore flessibilità (informazioni sui thread) in futuro. È anche giusto menzionare le vecchie classi buggy nei vecchi JDK.
Martin K.

5
ThreadGroup non implementa un join a livello di gruppo, quindi il motivo per cui le persone spingono ThreadGroup è un po 'sconcertante. Le persone stanno davvero usando spin lock e interrogano l'activeCount del gruppo? Sarebbe difficile convincermi che farlo è meglio in qualsiasi modo rispetto a chiamare semplicemente join su tutti i thread.

41

Un modo sarebbe creare un Listdi Threads, creare e avviare ogni thread, aggiungendolo all'elenco. Una volta avviato tutto, torna indietro nell'elenco e chiama join()ciascuno di essi. Non importa in quale ordine terminano l'esecuzione dei thread, tutto ciò che devi sapere è che nel momento in cui il secondo ciclo termina l'esecuzione, ogni thread sarà completato.

Un approccio migliore consiste nell'usare un ExecutorService e i suoi metodi associati:

List<Callable> callables = ... // assemble list of Callables here
                               // Like Runnable but can return a value
ExecutorService execSvc = Executors.newCachedThreadPool();
List<Future<?>> results = execSvc.invokeAll(callables);
// Note: You may not care about the return values, in which case don't
//       bother saving them

L'utilizzo di un ExecutorService (e di tutte le novità delle utilità di concorrenza di Java 5 ) è incredibilmente flessibile e l'esempio precedente graffia appena la superficie.


ThreadGroup è la strada da percorrere! Con una lista mutevole ti troverai nei guai (sincronizzazione)
Martin K.

3
Che cosa? Come ti metteresti nei guai? È modificabile (solo leggibile) dal thread che esegue il lancio, quindi finché non modifica l'elenco durante l' iterazione, va bene.
Adam Batkin

Dipende da come lo usi. Se utilizzerai la classe chiamante in un thread avrai problemi.
Martin K.

27
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class DoSomethingInAThread implements Runnable
{
   public static void main(String[] args) throws ExecutionException, InterruptedException
   {
      //limit the number of actual threads
      int poolSize = 10;
      ExecutorService service = Executors.newFixedThreadPool(poolSize);
      List<Future<Runnable>> futures = new ArrayList<Future<Runnable>>();

      for (int n = 0; n < 1000; n++)
      {
         Future f = service.submit(new DoSomethingInAThread());
         futures.add(f);
      }

      // wait for all tasks to complete before continuing
      for (Future<Runnable> f : futures)
      {
         f.get();
      }

      //shut down the executor service so that this thread can exit
      service.shutdownNow();
   }

   public void run()
   {
      // do something here
   }
}

ha funzionato a meraviglia ... ho due serie di thread che non dovrebbero essere eseguiti contemporaneamente a causa di problemi su più cookie. Ho usato il tuo esempio per eseguire una serie di thread alla volta .. grazie per aver condiviso le tue conoscenze ...
arn-arn

@Dantalian - Nella tua classe Runnable (probabilmente nel metodo run), vorresti catturare tutte le eccezioni che si sono verificate e memorizzarle localmente (o memorizzare un messaggio / condizione di errore). Nell'esempio, f.get () restituisce l'oggetto che hai inviato a ExecutorService. Il tuo oggetto potrebbe avere un metodo per recuperare eventuali eccezioni / condizioni di errore. A seconda di come si modifica l'esempio fornito, potrebbe essere necessario eseguire il cast dell'oggetto trasformato da f.get () nel tipo previsto.
jt.

12

invece di join(), che è una vecchia API, puoi usare CountDownLatch . Ho modificato il tuo codice come di seguito per soddisfare le tue esigenze.

import java.util.concurrent.*;
class DoSomethingInAThread implements Runnable{
    CountDownLatch latch;
    public DoSomethingInAThread(CountDownLatch latch){
        this.latch = latch;
    } 
    public void run() {
        try{
            System.out.println("Do some thing");
            latch.countDown();
        }catch(Exception err){
            err.printStackTrace();
        }
    }
}

public class CountDownLatchDemo {
    public static void main(String[] args) {
        try{
            CountDownLatch latch = new CountDownLatch(1000);
            for (int n=0; n<1000; n++) {
                Thread t = new Thread(new DoSomethingInAThread(latch));
                t.start();
            }
            latch.await();
            System.out.println("In Main thread after completion of 1000 threads");
        }catch(Exception err){
            err.printStackTrace();
        }
    }
}

Spiegazione :

  1. CountDownLatch è stato inizializzato con un conteggio specificato 1000 secondo le vostre esigenze.

  2. Ogni thread di lavoro DoSomethingInAThread decrementerà il CountDownLatch, che è stato passato nel costruttore.

  3. Filo principale CountDownLatchDemo await()fino a quando il conteggio è diventato zero. Una volta che il conteggio è diventato zero, otterrai l'output sotto la riga.

    In Main thread after completion of 1000 threads

Maggiori informazioni dalla pagina della documentazione di Oracle

public void await()
           throws InterruptedException

Fa sì che il thread corrente attenda fino a quando il latch non è arrivato a zero, a meno che il thread non venga interrotto.

Fare riferimento alla domanda SE correlata per altre opzioni:

aspetta che tutti i thread finiscano il loro lavoro in java


8

Evita del tutto la classe Thread e utilizza invece le astrazioni superiori fornite in java.util.concurrent

La classe ExecutorService fornisce il metodo invokeAll che sembra fare proprio quello che vuoi.


6

Considera l'utilizzo java.util.concurrent.CountDownLatch. Esempi in javadocs


È un latch per i thread, il latch lock funziona con un conto alla rovescia. Nel metodo run () del tuo thread dichiari esplicitamente di attendere che un CountDownLatch raggiunga il suo conto alla rovescia a 0. Puoi usare lo stesso CountDownLatch in più di un thread per rilasciarli simultaneamente. Non so se è quello che ti serve, volevo solo menzionarlo perché è utile quando si lavora in un ambiente multithread.
Pablo Cavalieri

Forse dovresti mettere quella spiegazione nel corpo della tua risposta?
Aaron Hall

Gli esempi in Javadoc sono molto descrittivi, ecco perché non ne ho aggiunti. docs.oracle.com/javase/7/docs/api/java/util/concurrent/… . Nel primo esempio, tutti i thread di lavoro vengono rilasciati contemporaneamente perché attendono che CountdownLatch startSignal raggiunga lo zero, cosa che accade in startSignal.countDown (). Quindi, il thread mian attende che tutti i lavori finiscano utilizzando l'istruzione doneSignal.await (). doneSignal diminuisce il suo valore in ogni lavoratore.
Pablo Cavalieri

6

Come suggerito da Martin K java.util.concurrent.CountDownLatchsembra essere una soluzione migliore per questo. Aggiungo solo un esempio per lo stesso

     public class CountDownLatchDemo
{

    public static void main (String[] args)
    {
        int noOfThreads = 5;
        // Declare the count down latch based on the number of threads you need
        // to wait on
        final CountDownLatch executionCompleted = new CountDownLatch(noOfThreads);
        for (int i = 0; i < noOfThreads; i++)
        {
            new Thread()
            {

                @Override
                public void run ()
                {

                    System.out.println("I am executed by :" + Thread.currentThread().getName());
                    try
                    {
                        // Dummy sleep
                        Thread.sleep(3000);
                        // One thread has completed its job
                        executionCompleted.countDown();
                    }
                    catch (InterruptedException e)
                    {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }

            }.start();
        }

        try
        {
            // Wait till the count down latch opens.In the given case till five
            // times countDown method is invoked
            executionCompleted.await();
            System.out.println("All over");
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }

}

4

A seconda delle tue esigenze, potresti anche voler controllare le classi CountDownLatch e CyclicBarrier nel pacchetto java.util.concurrent. Possono essere utili se vuoi che i tuoi thread si aspettino l'uno per l'altro, o se vuoi un controllo più dettagliato sul modo in cui i tuoi thread vengono eseguiti (ad esempio, aspettando nella loro esecuzione interna che un altro thread imposti uno stato). Puoi anche usare un CountDownLatch per segnalare a tutti i tuoi thread di iniziare allo stesso tempo, invece di avviarli uno per uno mentre ripeti il ​​tuo ciclo. I documenti API standard hanno un esempio di questo, oltre a utilizzare un altro CountDownLatch per attendere che tutti i thread completino la loro esecuzione.



1

Crea l'oggetto thread all'interno del primo ciclo for.

for (int i = 0; i < threads.length; i++) {
     threads[i] = new Thread(new Runnable() {
         public void run() {
             // some code to run in parallel
         }
     });
     threads[i].start();
 }

E poi così quello che dicono tutti qui.

for(i = 0; i < threads.length; i++)
  threads[i].join();

0

Non sono sicuro di come proponi esattamente di farlo. Se proponi di eseguire il polling di activeCount in un ciclo: non va bene, poiché è un'attesa impegnativa (anche se dormi tra i sondaggi, ottieni un compromesso tra business e reattività).
Martin v. Löwis

@Martin v. Löwis: "Join attenderà un solo thread. Una soluzione migliore potrebbe essere java.util.concurrent.CountDownLatch. Inizializza semplicemente il latch con il conteggio impostato sul numero di thread di lavoro. Ogni thread di lavoro dovrebbe chiamare countDown () appena prima che esca e il thread principale chiama semplicemente await (), che si bloccherà fino a quando il contatore non raggiunge zero. Il problema con join () è anche che non puoi iniziare ad aggiungere più thread dinamicamente. L'elenco esploderà con una modifica simultanea. " La tua soluzione funziona bene per il problema ma non per scopi generali.
Martin K.

0

In alternativa a CountDownLatch puoi anche usare CyclicBarrier es

public class ThreadWaitEx {
    static CyclicBarrier barrier = new CyclicBarrier(100, new Runnable(){
        public void run(){
            System.out.println("clean up job after all tasks are done.");
        }
    });
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            Thread t = new Thread(new MyCallable(barrier));
            t.start();
        }       
    }

}    

class MyCallable implements Runnable{
    private CyclicBarrier b = null;
    public MyCallable(CyclicBarrier b){
        this.b = b;
    }
    @Override
    public void run(){
        try {
            //do something
            System.out.println(Thread.currentThread().getName()+" is waiting for barrier after completing his job.");
            b.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
    }       
}

Per usare CyclicBarrier in questo caso, barrier.await () dovrebbe essere l'ultima istruzione, cioè quando il thread ha terminato il suo lavoro. CyclicBarrier può essere utilizzato di nuovo con il suo metodo reset (). Per citare javadoc:

Un CyclicBarrier supporta un comando Runnable opzionale che viene eseguito una volta per punto barriera, dopo l'arrivo dell'ultimo thread nel gruppo, ma prima che venga rilasciato qualsiasi thread. Questa azione barriera è utile per aggiornare lo stato condiviso prima che una delle parti continui.


Non penso che sia un buon esempio per CyclicBarrier. Perché usi una chiamata Thread.sleep ()?
Guenther

@Guenther - sì, ho cambiato il codice per adattarlo ai requisiti.
shailendra1118

CyclicBarrier non è un'alternativa a CountDownLatch. Quando i thread devono eseguire ripetutamente il conto alla rovescia, è necessario creare un CyclicBarrier, altrimenti il ​​valore predefinito è CountDownLatch (a meno che non sia richiesto altrimenti un'astrazione aggiuntiva di Execution, a quel punto dovresti guardare al livello superiore, Services).
Elysiumplain,

0

Il join()non è stato utile per me. guarda questo esempio in Kotlin:

    val timeInMillis = System.currentTimeMillis()
    ThreadUtils.startNewThread(Runnable {
        for (i in 1..5) {
            val t = Thread(Runnable {
                Thread.sleep(50)
                var a = i
                kotlin.io.println(Thread.currentThread().name + "|" + "a=$a")
                Thread.sleep(200)
                for (j in 1..5) {
                    a *= j
                    Thread.sleep(100)
                    kotlin.io.println(Thread.currentThread().name + "|" + "$a*$j=$a")
                }
                kotlin.io.println(Thread.currentThread().name + "|TaskDurationInMillis = " + (System.currentTimeMillis() - timeInMillis))
            })
            t.start()
        }
    })

Il risultato:

Thread-5|a=5
Thread-1|a=1
Thread-3|a=3
Thread-2|a=2
Thread-4|a=4
Thread-2|2*1=2
Thread-3|3*1=3
Thread-1|1*1=1
Thread-5|5*1=5
Thread-4|4*1=4
Thread-1|2*2=2
Thread-5|10*2=10
Thread-3|6*2=6
Thread-4|8*2=8
Thread-2|4*2=4
Thread-3|18*3=18
Thread-1|6*3=6
Thread-5|30*3=30
Thread-2|12*3=12
Thread-4|24*3=24
Thread-4|96*4=96
Thread-2|48*4=48
Thread-5|120*4=120
Thread-1|24*4=24
Thread-3|72*4=72
Thread-5|600*5=600
Thread-4|480*5=480
Thread-3|360*5=360
Thread-1|120*5=120
Thread-2|240*5=240
Thread-1|TaskDurationInMillis = 765
Thread-3|TaskDurationInMillis = 765
Thread-4|TaskDurationInMillis = 765
Thread-5|TaskDurationInMillis = 765
Thread-2|TaskDurationInMillis = 765

Ora fammi usare i join()thread per:

    val timeInMillis = System.currentTimeMillis()
    ThreadUtils.startNewThread(Runnable {
        for (i in 1..5) {
            val t = Thread(Runnable {
                Thread.sleep(50)
                var a = i
                kotlin.io.println(Thread.currentThread().name + "|" + "a=$a")
                Thread.sleep(200)
                for (j in 1..5) {
                    a *= j
                    Thread.sleep(100)
                    kotlin.io.println(Thread.currentThread().name + "|" + "$a*$j=$a")
                }
                kotlin.io.println(Thread.currentThread().name + "|TaskDurationInMillis = " + (System.currentTimeMillis() - timeInMillis))
            })
            t.start()
            t.join()
        }
    })

E il risultato:

Thread-1|a=1
Thread-1|1*1=1
Thread-1|2*2=2
Thread-1|6*3=6
Thread-1|24*4=24
Thread-1|120*5=120
Thread-1|TaskDurationInMillis = 815
Thread-2|a=2
Thread-2|2*1=2
Thread-2|4*2=4
Thread-2|12*3=12
Thread-2|48*4=48
Thread-2|240*5=240
Thread-2|TaskDurationInMillis = 1568
Thread-3|a=3
Thread-3|3*1=3
Thread-3|6*2=6
Thread-3|18*3=18
Thread-3|72*4=72
Thread-3|360*5=360
Thread-3|TaskDurationInMillis = 2323
Thread-4|a=4
Thread-4|4*1=4
Thread-4|8*2=8
Thread-4|24*3=24
Thread-4|96*4=96
Thread-4|480*5=480
Thread-4|TaskDurationInMillis = 3078
Thread-5|a=5
Thread-5|5*1=5
Thread-5|10*2=10
Thread-5|30*3=30
Thread-5|120*4=120
Thread-5|600*5=600
Thread-5|TaskDurationInMillis = 3833

Come è chiaro quando usiamo join:

  1. I thread vengono eseguiti in sequenza.
  2. Il primo campione richiede 765 millisecondi mentre il secondo campione richiede 3833 millisecondi.

La nostra soluzione per impedire il blocco di altri thread è stata la creazione di un ArrayList:

val threads = ArrayList<Thread>()

Ora, quando vogliamo iniziare un nuovo thread, lo aggiungiamo maggiormente a ArrayList:

addThreadToArray(
    ThreadUtils.startNewThread(Runnable {
        ...
    })
)

La addThreadToArrayfunzione:

@Synchronized
fun addThreadToArray(th: Thread) {
    threads.add(th)
}

La funzione startNewThread:

fun startNewThread(runnable: Runnable) : Thread {
    val th = Thread(runnable)
    th.isDaemon = false
    th.priority = Thread.MAX_PRIORITY
    th.start()
    return th
}

Controlla il completamento dei thread come di seguito ovunque sia necessario:

val notAliveThreads = ArrayList<Thread>()
for (t in threads)
    if (!t.isAlive)
        notAliveThreads.add(t)
threads.removeAll(notAliveThreads)
if (threads.size == 0){
    // The size is 0 -> there is no alive threads.
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.