ExecutorService: in attesa del completamento dei thread

1. Panoramica

Il framework ExecutorService semplifica l'elaborazione delle attività in più thread. Stiamo per esemplificare alcuni scenari in cui aspettiamo che i thread finiscano la loro esecuzione.

Inoltre, mostreremo come arrestare correttamente un ExecutorService e attendere che i thread già in esecuzione finiscano la loro esecuzione.

2. Dopo l' arresto di Executor

Quando si utilizza un Executor, è possibile spegnerlo chiamando i metodi shutdown () o shutdownNow () . Tuttavia, non aspetterà che tutti i thread smettano di essere eseguiti.

Attendere che i thread esistenti completino la loro esecuzione può essere ottenuto utilizzando il metodo awaitTermination () .

Questo blocca il thread fino a quando tutte le attività non completano la loro esecuzione o non viene raggiunto il timeout specificato:

public void awaitTerminationAfterShutdown(ExecutorService threadPool) { threadPool.shutdown(); try { if (!threadPool.awaitTermination(60, TimeUnit.SECONDS)) { threadPool.shutdownNow(); } } catch (InterruptedException ex) { threadPool.shutdownNow(); Thread.currentThread().interrupt(); } }

3. Utilizzo di CountDownLatch

Successivamente, esaminiamo un altro approccio per risolvere questo problema, utilizzando un CountDownLatch per segnalare il completamento di un'attività.

Possiamo inizializzarlo con un valore che rappresenta il numero di volte che può essere decrementato prima che tutti i thread, che hanno chiamato il metodo await () , vengano notificati.

Ad esempio, se abbiamo bisogno che il thread corrente attenda che altri N thread finiscano la loro esecuzione, possiamo inizializzare il latch usando N :

ExecutorService WORKER_THREAD_POOL = Executors.newFixedThreadPool(10); CountDownLatch latch = new CountDownLatch(2); for (int i = 0; i  { try { // ... latch.countDown(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); } // wait for the latch to be decremented by the two remaining threads latch.await();

4. Utilizzo di invokeAll ()

Il primo approccio che possiamo usare per eseguire i thread è il metodo invokeAll () . Il metodo restituisce un elenco di oggetti Future al termine di tutte le attività o al termine del timeout .

Inoltre, dobbiamo notare che l'ordine degli oggetti Future restituiti è lo stesso dell'elenco degli oggetti Callable forniti :

ExecutorService WORKER_THREAD_POOL = Executors.newFixedThreadPool(10); List
    
      callables = Arrays.asList( new DelayedCallable("fast thread", 100), new DelayedCallable("slow thread", 3000)); long startProcessingTime = System.currentTimeMillis(); List
     
       futures = WORKER_THREAD_POOL.invokeAll(callables); awaitTerminationAfterShutdown(WORKER_THREAD_POOL); long totalProcessingTime = System.currentTimeMillis() - startProcessingTime; assertTrue(totalProcessingTime >= 3000); String firstThreadResponse = futures.get(0).get(); assertTrue("fast thread".equals(firstThreadResponse)); String secondThreadResponse = futures.get(1).get(); assertTrue("slow thread".equals(secondThreadResponse));
     
    

5. Utilizzo di ExecutorCompletionService

Un altro approccio all'esecuzione di più thread consiste nell'usare ExecutorCompletionService. Utilizza un ExecutorService fornito per eseguire le attività.

Una differenza rispetto a invokeAll () è l'ordine in cui vengono restituiti i Futures, che rappresentano le attività eseguite. ExecutorCompletionService utilizza una coda per memorizzare i risultati nell'ordine in cui sono finiti , mentre invokeAll () restituisce un elenco con lo stesso ordine sequenziale prodotto dall'iteratore per l'elenco di attività specificato:

CompletionService service = new ExecutorCompletionService(WORKER_THREAD_POOL); List
    
      callables = Arrays.asList( new DelayedCallable("fast thread", 100), new DelayedCallable("slow thread", 3000)); for (Callable callable : callables) { service.submit(callable); } 
    

È possibile accedere ai risultati utilizzando il metodo take () :

long startProcessingTime = System.currentTimeMillis(); Future future = service.take(); String firstThreadResponse = future.get(); long totalProcessingTime = System.currentTimeMillis() - startProcessingTime; assertTrue("First response should be from the fast thread", "fast thread".equals(firstThreadResponse)); assertTrue(totalProcessingTime >= 100 && totalProcessingTime = 3000 && totalProcessingTime < 4000); LOG.debug("Thread finished after: " + totalProcessingTime + " milliseconds"); awaitTerminationAfterShutdown(WORKER_THREAD_POOL);

6. Conclusione

A seconda del caso d'uso, abbiamo varie opzioni per attendere che i thread finiscano la loro esecuzione.

Un CountDownLatch è utile quando è necessario un meccanismo per notificare a uno o più thread che una serie di operazioni eseguite da altri thread è terminata.

ExecutorCompletionService è utile quando è necessario accedere al risultato dell'attività il prima possibile e altri approcci quando si desidera attendere il completamento di tutte le attività in esecuzione.

Il codice sorgente dell'articolo è disponibile su GitHub.