ExecutorService, come attendere il completamento di tutte le attività


198

Qual è il modo più semplice di attendere il completamento di tutte le attività ExecutorService? La mia attività è principalmente computazionale, quindi voglio solo eseguire un gran numero di lavori, uno su ciascun core. In questo momento la mia configurazione è simile al seguente:

ExecutorService es = Executors.newFixedThreadPool(2);
for (DataTable singleTable : uniquePhrases) {   
    es.execute(new ComputeDTask(singleTable));
}
try{
    es.wait();
} 
catch (InterruptedException e){
    e.printStackTrace();
}

ComputeDTaskimplementa eseguibile. Questo sembra eseguire correttamente le attività, ma il codice si blocca wait()con IllegalMonitorStateException. Questo è strano, perché ho giocato con alcuni esempi di giocattoli e sembrava funzionare.

uniquePhrasescontiene diverse decine di migliaia di elementi. Dovrei usare un altro metodo? Sto cercando qualcosa il più semplice possibile


1
se vuoi usare wait (le risposte ti dicono che non lo fai): devi sempre sincronizzarti sull'oggetto (in questo caso es) quando vuoi aspettare - il blocco verrà automaticamente rilasciato durante l'attesa
mihi

13
un modo migliore per inizializzare il ThreadPool èExecutors.newFixedThreadPool(System.getRuntime().availableProcessors());

7
Runtime.getRuntime () availableProcessors ().;
Vik Gamov,

[Questo] [1] è un'alternativa interessante .. [1]: stackoverflow.com/questions/1250643/...
Răzvan Petruescu

1
se si desidera utilizzare CountDownLatch, questo è il codice di esempio: stackoverflow.com/a/44127101/4069305
Tuan Pham,

Risposte:


213

L'approccio più semplice è usare quello ExecutorService.invokeAll()che fa quello che vuoi in un one-liner. Nel tuo linguaggio, dovrai modificare o avvolgere ComputeDTaskper implementare Callable<>, il che può darti un po 'più di flessibilità. Probabilmente nella tua app c'è un'implementazione significativa di Callable.call(), ma ecco un modo per avvolgerla se non si utilizza Executors.callable().

ExecutorService es = Executors.newFixedThreadPool(2);
List<Callable<Object>> todo = new ArrayList<Callable<Object>>(singleTable.size());

for (DataTable singleTable: uniquePhrases) { 
    todo.add(Executors.callable(new ComputeDTask(singleTable))); 
}

List<Future<Object>> answers = es.invokeAll(todo);

Come altri hanno sottolineato, è possibile utilizzare la versione di timeout invokeAll()se appropriato. In questo esempio, answersconterrà un gruppo di Futures che restituiranno valori nulli (vedi definizione di Executors.callable(). Probabilmente quello che vuoi fare è un leggero refactoring in modo da poter ottenere una risposta utile o un riferimento al sottostante ComputeDTask, ma posso non dire dal tuo esempio.

Se non è chiaro, notare che invokeAll()non tornerà fino a quando tutte le attività non saranno completate. (vale a dire, tutti Futurei messaggi nella tua answersraccolta segnaleranno .isDone()se richiesto.) Questo evita tutti gli arresti manuali, waitTermination, ecc ... e ti consente di riutilizzarli ExecutorServiceordinatamente per più cicli, se lo desideri.

Ci sono alcune domande correlate su SO:

Nessuno di questi è strettamente pertinente alla tua domanda, ma fornisce un po 'di colore su come la gente pensa Executor/ ExecutorServicedovrebbe essere usata.


9
Questo è perfetto se stai aggiungendo tutti i tuoi lavori in un batch e ti blocchi sull'elenco di Callables, ma non funzionerà se stai chiamando ExecutorService.submit () in una situazione di richiamata o loop di eventi.
Desty,

2
Penso che valga la pena ricordare che shutdown () dovrebbe ancora essere chiamato quando ExecutorService non è più necessario, altrimenti i thread non si chiuderanno mai (tranne per i casi in cui corePoolSize = 0 o allowCoreThreadTimeOut = true).
Giovanni 29

sorprendente! Proprio quello che stavo cercando. Grazie mille per aver condiviso la risposta. Fammi provare.
MohamedSanaulla,

59

Se si desidera attendere il completamento di tutte le attività, utilizzare il shutdownmetodo anziché wait. Quindi seguilo con awaitTermination.

Inoltre, puoi utilizzare Runtime.availableProcessorsper ottenere il numero di thread hardware in modo da poter inizializzare correttamente il tuo pool di thread.


27
shutdown () impedisce a ExecutorService di accettare nuove attività e chiude i thread di lavoro inattivi. Non si specifica di attendere il completamento dell'arresto e l'implementazione in ThreadPoolExecutor non attende.
Alain O'Dea,

1
@Alain - grazie. Avrei dovuto menzionare waititTermination. Fisso.
GN.

5
Che cosa succede se per completare un'attività è necessario pianificare ulteriori attività? Ad esempio, è possibile effettuare un attraversamento di alberi multithread che passa i rami ai thread di lavoro. In tal caso, poiché ExecutorService viene chiuso all'istante non riesce ad accettare lavori programmati in modo ricorsivo.
Brian Gordon,

2
awaitTerminationrichiede un timeout come parametro. Mentre è possibile fornire un tempo finito e posizionare un loop attorno ad esso per attendere fino al termine di tutti i thread, mi chiedevo se ci fosse una soluzione più elegante.
Abhishek S,

1
Hai ragione, ma vedi questa risposta - stackoverflow.com/a/1250655/263895 - potresti sempre dargli un timeout incredibilmente lungo
NG.

48

Se attendere il completamento di tutte le attività ExecutorServicenon è esattamente il tuo obiettivo, ma piuttosto attendere il completamento di un determinato gruppo di attività , puoi utilizzare un CompletionService- in particolare, un ExecutorCompletionService.

L'idea è quella di creare un ExecutorCompletionServicewrapping Executor, inviare un numero noto di attività attraverso il CompletionService, quindi disegnare lo stesso numero di risultati dalla coda di completamento usando take()(quali blocchi) o poll()(che non lo fanno). Dopo aver disegnato tutti i risultati previsti corrispondenti alle attività che hai inviato, sai che sono stati completati.

Lasciatemelo dire ancora una volta, perché dall'interfaccia non è ovvio: devi sapere quante cose metti dentro CompletionServiceper sapere quante cose provare a disegnare. Questo è importante soprattutto con il take()metodo: chiamalo una volta in più e bloccherà il tuo thread di chiamata fino a quando un altro thread non invierà un altro lavoro allo stesso CompletionService.

Ci sono alcuni esempi che mostrano come utilizzareCompletionService nel libro Java Concurrency in Practice .


Questo è un buon contrappunto alla mia risposta - direi che la risposta semplice alla domanda è invokeAll (); ma @seh ha ragione quando presenta gruppi di lavori all'ES e aspetta che vengano completati ... --JA
andersoj

@ om-nom-nom, grazie per aver aggiornato i collegamenti. Sono contento di vedere che la risposta è ancora utile.
seh

1
Buona risposta, non ne ero a conoscenzaCompletionService
Vic

1
Questo è l'approccio da utilizzare, se non si desidera arrestare un ExecutorService esistente, ma si desidera solo inviare un batch di attività e sapere quando sono state completate.
ToolmakerSteve

11

Se si desidera attendere il completamento dell'esecuzione del servizio di esecuzione, chiamare shutdown()e quindi attendere il termine (unità, tipo unità) , ad es awaitTermination(1, MINUTE). ExecutorService non si blocca sul proprio monitor, quindi non è possibile utilizzare waitecc.


Penso che sia in attesa della conclusione.
GN.

@SB - Grazie - Vedo che la mia memoria è fallibile! Ho aggiornato il nome e ho aggiunto un link per essere sicuro.
mdma,

Per attendere "per sempre", utilizzarlo come awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS); stackoverflow.com/a/1250655/32453
rogerdpack

Penso che questo sia l'approccio più semplice
Shervin Asgari,

1
@MosheElisha, sei sicuro? docs.oracle.com/javase/8/docs/api/java/util/concurrent/… dice Avvia un arresto ordinato in cui vengono eseguite attività precedentemente inviate, ma non verranno accettate nuove attività.
Jaime Hablutzel,

7

È possibile attendere che i lavori terminino in un determinato intervallo:

int maxSecondsPerComputeDTask = 20;
try {
    while (!es.awaitTermination(uniquePhrases.size() * maxSecondsPerComputeDTask, TimeUnit.SECONDS)) {
        // consider giving up with a 'break' statement under certain conditions
    }
} catch (InterruptedException e) {
    throw new RuntimeException(e);    
}

Oppure potresti usare ExecutorService . submit ( Runnable ) e raccoglie gli oggetti Future che restituisce e chiama get () su ciascuno a sua volta per attendere che finiscano.

ExecutorService es = Executors.newFixedThreadPool(2);
Collection<Future<?>> futures = new LinkedList<<Future<?>>();
for (DataTable singleTable : uniquePhrases) {
    futures.add(es.submit(new ComputeDTask(singleTable)));
}
for (Future<?> future : futures) {
   try {
       future.get();
   } catch (InterruptedException e) {
       throw new RuntimeException(e);
   } catch (ExecutionException e) {
       throw new RuntimeException(e);
   }
}

InterruptedException è estremamente importante da gestire correttamente. È ciò che consente a te o agli utenti della tua libreria di terminare un lungo processo in modo sicuro.


6

Basta usare

latch = new CountDownLatch(noThreads)

In ogni thread

latch.countDown();

e come barriera

latch.await();

6

Causa principale per IllegalMonitorStateException :

Generato per indicare che un thread ha tentato di attendere sul monitor di un oggetto o di avvisare altri thread in attesa sul monitor di un oggetto senza possedere il monitor specificato.

Dal tuo codice, hai appena chiamato wait () su ExecutorService senza possedere il blocco.

Di seguito il codice risolverà IllegalMonitorStateException

try 
{
    synchronized(es){
        es.wait(); // Add some condition before you call wait()
    }
} 

Seguire uno degli approcci seguenti per attendere il completamento di tutte le attività a cui sono state inviate ExecutorService.

  1. Scorrere tutte le Futureattività da submitsu ExecutorServicee controllare lo stato di blocco di chiamata get()in Futureoggetto

  2. Utilizzando invokeAll onExecutorService

  3. Utilizzando CountDownLatch

  4. Uso di ForkJoinPool o newWorkStealingPool di Executors(da java 8)

  5. Arrestare il pool come raccomandato nella pagina della documentazione di Oracle

    void shutdownAndAwaitTermination(ExecutorService pool) {
       pool.shutdown(); // Disable new tasks from being submitted
       try {
       // Wait a while for existing tasks to terminate
       if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
           pool.shutdownNow(); // Cancel currently executing tasks
           // Wait a while for tasks to respond to being cancelled
           if (!pool.awaitTermination(60, TimeUnit.SECONDS))
           System.err.println("Pool did not terminate");
       }
    } catch (InterruptedException ie) {
         // (Re-)Cancel if current thread also interrupted
         pool.shutdownNow();
         // Preserve interrupt status
         Thread.currentThread().interrupt();
    }

    Se si desidera attendere con grazia il completamento di tutte le attività quando si utilizza l'opzione 5 anziché le opzioni da 1 a 4, modificare

    if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {

    per

    a while(condition)che controlla ogni 1 minuto.


6

Puoi usare il ExecutorService.invokeAllmetodo, eseguirà tutte le attività e attenderà che tutti i thread abbiano terminato la loro attività.

Ecco javadoc completo

È anche possibile utilizzare la versione sovraccarica dell'utente di questo metodo per specificare il timeout.

Ecco un codice di esempio con ExecutorService.invokeAll

public class Test {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService service = Executors.newFixedThreadPool(3);
        List<Callable<String>> taskList = new ArrayList<>();
        taskList.add(new Task1());
        taskList.add(new Task2());
        List<Future<String>> results = service.invokeAll(taskList);
        for (Future<String> f : results) {
            System.out.println(f.get());
        }
    }

}

class Task1 implements Callable<String> {
    @Override
    public String call() throws Exception {
        try {
            Thread.sleep(2000);
            return "Task 1 done";
        } catch (Exception e) {
            e.printStackTrace();
            return " error in task1";
        }
    }
}

class Task2 implements Callable<String> {
    @Override
    public String call() throws Exception {
        try {
            Thread.sleep(3000);
            return "Task 2 done";
        } catch (Exception e) {
            e.printStackTrace();
            return " error in task2";
        }
    }
}

3

Ho anche la situazione in cui ho una serie di documenti da sottoporre a scansione. Comincio con un documento "seed" iniziale che dovrebbe essere elaborato, quel documento contiene collegamenti ad altri documenti che dovrebbero anche essere elaborati e così via.

Nel mio programma principale, voglio solo scrivere qualcosa di simile al seguente, dove Crawlercontrolla un sacco di thread.

Crawler c = new Crawler();
c.schedule(seedDocument); 
c.waitUntilCompletion()

La stessa situazione accadrebbe se volessi navigare su un albero; salterei nel nodo radice, il processore per ogni nodo aggiungerebbe i bambini alla coda, se necessario, e un gruppo di thread elaborerebbe tutti i nodi nella struttura, fino a quando non ce ne fossero più.

Non riuscivo a trovare nulla nella JVM che pensavo fosse un po 'sorprendente. Quindi ho scritto una classe ThreadPoolche si può usare direttamente o sottoclasse per aggiungere metodi adatti al dominio, ad es schedule(Document). Spero che sia d'aiuto!

ThreadPool Javadoc | Esperto di


Doc Link è morto
Manti_Core il

@Manti_Core - grazie, aggiornato.
Adrian Smith,

2

Aggiungi tutti i thread nella raccolta e invialo utilizzando invokeAll. Se è possibile utilizzare il invokeAllmetodo di ExecutorService, JVM non procederà alla riga successiva fino al completamento di tutti i thread.

Ecco un buon esempio: invokeAll tramite ExecutorService


1

Invia le tue attività nel Runner e attendi quindi chiamando il metodo waitTillDone () in questo modo:

Runner runner = Runner.runner(2);

for (DataTable singleTable : uniquePhrases) {

    runner.run(new ComputeDTask(singleTable));
}

// blocks until all tasks are finished (or failed)
runner.waitTillDone();

runner.shutdown();

Per usarlo aggiungi questa gradle / maven dipendenza: 'com.github.matejtymes:javafixes:1.0'

Per maggiori dettagli guarda qui: https://github.com/MatejTymes/JavaFixes o qui: http://matejtymes.blogspot.com/2016/04/executor-that-notify-you-when-task.html



0

Aspetterò solo che l'esecutore termini con un timeout specificato che ritieni sia adatto al completamento delle attività.

 try {  
         //do stuff here 
         exe.execute(thread);
    } finally {
        exe.shutdown();
    }
    boolean result = exe.awaitTermination(4, TimeUnit.HOURS);
    if (!result)

    {
        LOGGER.error("It took more than 4 hour for the executor to stop, this shouldn't be the normal behaviour.");
    }

0

Sembra che sia necessario ForkJoinPoole utilizzare il pool globale per eseguire attività.

public static void main(String[] args) {
    // the default `commonPool` should be sufficient for many cases.
    ForkJoinPool pool = ForkJoinPool.commonPool(); 
    // The root of your task that may spawn other tasks. 
    // Make sure it submits the additional tasks to the same executor that it is in.
    Runnable rootTask = new YourTask(pool); 
    pool.execute(rootTask);
    pool.awaitQuiescence(...);
    // that's it.
}

Il bello è che pool.awaitQuiescenceil metodo si bloccherà utilizzando il thread del chiamante per eseguire i suoi compiti e poi tornare quando è veramente vuoto.

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.