attendi che tutti i thread finiscano il loro lavoro in java


91

Sto scrivendo un'applicazione che ha 5 thread che ottengono alcune informazioni dal web contemporaneamente e riempiono 5 diversi campi in una classe buffer.
Ho bisogno di convalidare i dati del buffer e memorizzarli in un database quando tutti i thread hanno terminato il loro lavoro.
Come posso farlo (essere avvisato quando tutti i thread hanno terminato il loro lavoro)?


4
Thread.join è un modo idiosincratico Java di livello piuttosto basso per risolvere il problema. Inoltre è problematico perché l' API Thread è difettosa: non puoi sapere se il join è stato completato correttamente o meno (vedi Java Concurrency In Practice ). L'astrazione di livello più alto, come l'utilizzo di un CountDownLatch, può essere preferibile e apparirà più naturale ai programmatori che non sono "bloccati" nella mentalità idiosincratica di Java. Non discutere con me, vai a discutere con Doug Lea; )
Cedric Martin

Risposte:


119

L'approccio che adotto consiste nell'usare un ExecutorService per gestire i pool di thread.

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 questo è esattamente ciò che fa shutdown ().
Peter Lawrey

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

3
@AquariusPower Potresti semplicemente dirgli di aspettare più a lungo, o per sempre.
Peter Lawrey

1
Ah, ho capito; quindi ho aggiunto un messaggio nel ciclo dicendo che è in attesa che tutti i thread finiscano; Grazie!
Aquarius Power

1
@PeterLawrey, è necessario chiamare es.shutdown();? cosa succede se scrivo un codice in cui ho eseguito un thread utilizzando es.execute(runnableObj_ZipMaking);in tryblock e in finallyho chiamato boolean finshed = es.awaitTermination(10, TimeUnit.MINUTES);. Quindi suppongo che questo dovrebbe attendere fino a quando tutti i thread completano il loro lavoro o si verifica il timeout (qualunque sia il primo). La mia ipotesi è corretta? o la chiamata shutdown()è obbligatoria?
Amogh

52

Puoi joinai fili. Il join si blocca fino al completamento del thread.

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

Nota che joingenera un InterruptedException. Dovrai decidere cosa fare se ciò accade (ad esempio, prova a cancellare gli altri thread per evitare che venga svolto un lavoro non necessario).


1
Questi thread vengono eseguiti in parallelo o in sequenza l'uno con l'altro?
James Webster,

5
@JamesWebster: Parallela.
RYN

4
@ James Webster: L'istruzione t.join();significa che il thread corrente si blocca finché il thread non ttermina. Non influisce sul filo t.
Mark Byers

1
Grazie. =] Ho studiato parallelismo all'università, ma è stata l'unica cosa che ho faticato a imparare! Per fortuna non devo usarlo molto ora o quando lo faccio non è troppo complesso o non ci sono risorse condivise e il blocco non è fondamentale
James Webster

1
@ 4r1y4n Il fatto che il codice fornito sia veramente parallelo dipende da cosa si sta tentando di fare ed è più correlato all'aggregazione dei dati distribuiti nelle raccolte utilizzando thread uniti. Stai unendo thread, che potenzialmente significa "unire" dati. Inoltre, parallelismo NON significa necessariamente concorrenza. Dipende dalle CPU. Potrebbe benissimo essere che i thread siano in esecuzione in parallelo, ma i calcoli avvengono in qualsiasi ordine sia determinato dalla CPU sottostante.

22

Dai un'occhiata a varie soluzioni.

  1. join()L'API è stata introdotta nelle prime versioni di Java. Alcune buone alternative sono disponibili con questo pacchetto concorrente dalla versione 1.5 di JDK.

  2. ExecutorService # invokeAll ()

    Esegue le attività date, restituendo un elenco di Futures con il loro stato e risultati quando tutto è completato.

    Fare riferimento a questa domanda SE correlata per un esempio di codice:

    Come utilizzare invokeAll () per consentire a tutti i pool di thread di svolgere il proprio compito?

  3. CountDownLatch

    Un aiuto per la sincronizzazione che consente a uno o più thread di attendere fino al completamento di una serie di operazioni eseguite in altri thread.

    Un CountDownLatch viene inizializzato con un determinato conteggio. I metodi di wait si bloccano fino a quando il conteggio corrente raggiunge lo zero a causa di invocazioni del countDown()metodo, dopodiché tutti i thread in attesa vengono rilasciati e ogni successiva chiamata di await ritorna immediatamente. Questo è un fenomeno one-shot: il conteggio non può essere ripristinato. Se hai bisogno di una versione che azzeri il conteggio, considera l'utilizzo di un CyclicBarrier .

    Fare riferimento a questa domanda per l'utilizzo di CountDownLatch

    Come aspettare un thread che genera il proprio thread?

  4. ForkJoinPool o newWorkStealingPool () in Executors

  5. Scorri tutti gli oggetti Future creati dopo l'invio aExecutorService


11

Oltre a quanto Thread.join()suggerito da altri, java 5 ha introdotto il framework per esecutori. Lì non lavori con gli Threadoggetti. Invece, invii i tuoi oggetti Callableo Runnablea un esecutore. C'è un esecutore speciale che ha lo scopo di eseguire più attività e restituire i loro risultati fuori ordine. Questo è il ExecutorCompletionService:

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

Quindi puoi chiamare ripetutamente take()fino a quando non ci sono più Future<?>oggetti da restituire, il che significa che tutti sono stati completati.


Un'altra cosa che potrebbe essere rilevante, a seconda del tuo scenario è CyclicBarrier.

Un ausilio per la sincronizzazione che consente a un insieme di thread di attendere l'un l'altro per raggiungere un punto di barriera comune. CyclicBarriers sono utili nei programmi che coinvolgono un gruppo di thread di dimensioni fisse che occasionalmente devono aspettarsi l'uno per l'altro. La barriera è chiamata ciclica perché può essere riutilizzata dopo il rilascio dei thread in attesa.


Questo è vicino, ma farei comunque un paio di aggiustamenti. executor.submitrestituisce a Future<?>. Vorrei aggiungere questi futures a una lista, quindi scorrere l'elenco facendo appello geta ciascun futuro.
Ray

Inoltre, puoi istanziare un costruttore usando Executors, ad esempio, Executors.newCachedThreadPool(o simile)
Ray

10

Un'altra possibilità è l' CountDownLatchoggetto, che è utile per situazioni semplici: poiché si conosce in anticipo il numero di thread, lo si inizializza con il relativo conteggio e si passa il riferimento dell'oggetto a ciascun thread.
Al termine della sua attività, ogni thread chiama CountDownLatch.countDown()che decrementa il contatore interno. Il thread principale, dopo aver avviato tutti gli altri, dovrebbe eseguire la CountDownLatch.await()chiamata di blocco. Verrà rilasciato non appena il contatore interno avrà raggiunto lo 0.

Fai attenzione che con questo oggetto si InterruptedExceptionpossa lanciare anche un.


8

Tu fai

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

Dopo questo ciclo for, puoi essere certo che tutti i thread abbiano terminato il loro lavoro.


6

Attendi / blocca il thread principale finché alcuni altri thread non completano il loro lavoro.

Come @Ravindra babudetto può essere realizzato in vari modi, ma mostrando con esempi.

  • java.lang.Thread. join () Da: 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 da: 1.5

    • .countDown() «Decrementa il conteggio del gruppo latch.
    • .await() «I metodi di attesa si bloccano fino a quando il conteggio corrente raggiunge lo zero.

    Se hai creato, latchGroupCount = 4allora countDown()dovrebbe essere chiamato 4 volte per contare 0. Quindi, questo await()rilascerà i thread di blocco.

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

Codice di esempio della classe Threaded LatchTask. Per testare l'approccio utilizzare joiningThreads(); e latchThreads();dal metodo 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();
    }
}
  • CyclicBarrier Un ausilio per la sincronizzazione che consente a un insieme di thread di attendere l'uno dell'altro per raggiungere un punto di barriera comune. Le barre cicliche sono utili nei programmi che coinvolgono un gruppo di thread di dimensioni fisse che devono occasionalmente aspettarsi l'uno per l'altro. La barriera è chiamata ciclica perché può essere riutilizzata dopo il rilascio dei thread in attesa.
    CyclicBarrier barrier = new CyclicBarrier(3);
    barrier.await();
    Ad esempio, fare riferimento a questa classe Concurrent_ParallelNotifyies .

  • Framework esecutore : possiamo usare ExecutorService per creare un pool di thread e traccia lo stato di avanzamento delle attività asincrone con Future.

    • submit(Runnable), submit(Callable)che restituiscono Future Object. Usando la future.get()funzione possiamo bloccare il thread principale fino a quando i thread di lavoro non completano il suo lavoro.

    • invokeAll(...) - restituisce un elenco di oggetti Future tramite il quale è possibile ottenere i risultati delle esecuzioni di ogni Callable.

Trova un esempio di utilizzo di Interfaces Runnable, Callable with Executor framework.


@Guarda anche


4

Memorizza gli oggetti Thread in una raccolta (come un List o un Set), quindi esegui il ciclo attraverso la raccolta una volta avviati i thread e chiama join () sui Thread.



2

Sebbene non sia rilevante per il problema dell'OP, se sei interessato alla sincronizzazione (più precisamente, un rendez-vous) con esattamente un thread, puoi usare uno scambiatore

Nel mio caso, avevo bisogno di mettere in pausa il thread genitore fino a quando il thread figlio non ha fatto qualcosa, ad es. Ha completato la sua inizializzazione. Anche un CountDownLatch funziona bene.



1

prova questo, funzionerà.

  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

Ho avuto un problema simile e ho finito per utilizzare Java 8 parallelStream.

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

È semplicissimo e leggibile. Dietro le quinte sta usando il pool di fork join predefinito della JVM, il che significa che attenderà che tutti i thread finiscano prima di continuare. Per il mio caso è stata una buona soluzione, perché era l'unico parallelStream nella mia applicazione. Se hai più di un parallelStream in esecuzione contemporaneamente, leggi il link sottostante.

Maggiori informazioni sui flussi paralleli qui .


0

Le risposte esistenti hanno detto che potrebbe join()ogni thread.

Ma ci sono diversi modi per ottenere l'array / elenco di thread:

  • Aggiungi il thread in un elenco durante la creazione.
  • Utilizzare ThreadGroupper gestire i thread.

Il codice seguente utilizzerà l' ThreadGruopapproccio. Crea prima un gruppo, quindi quando crei ogni thread specifica il gruppo nel costruttore, in seguito potrebbe ottenere l'array di thread tramiteThreadGroup.enumerate()


Codice

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;
        }
    }
}

Il thread principale attenderà che tutti i thread nel gruppo finiscano.


0

Ho creato un piccolo metodo di supporto per attendere il completamento di alcuni thread:

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

-1

Usa questo nel thread principale: while (! Executor.isTerminated ()); Inserisci questa riga di codice dopo aver avviato tutti i thread dal servizio esecutore. Questo avvierà il thread principale solo dopo che tutti i thread avviati dagli esecutori saranno terminati. Assicurati di chiamare executor.shutdown (); prima del ciclo precedente.


Questa è l'attesa attiva, che farà sì che la CPU esegua costantemente un ciclo vuoto. Molto dispendioso.
Adam Michalik
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.