Consideriamo un semplice programma:


public static void main(String[] args) throws Exception {
	// Create an ExecutorService with a fixed number of threads: three
	ExecutorService service = Executors.newFixedThreadPool(3);
 
	// Pass a simple Runnable task to the ExecutorService
	service.submit(() -> System.out.println("done"));
}

L'esecuzione del programma produce l'output della console che ci aspettiamo:

Fatto

Ma questo non è seguito dall'output che di solito vediamo in IntelliJ IDEA:

Processo terminato con codice di uscita 0

Di solito lo vediamo quando un programma finisce.

Perché succede?

La descrizione del metodo newFixedThreadPool() ci dice che i thread creati utilizzando un ExecutorService continuano ad esistere fino a quando non vengono fermati in modo esplicito. Ciò significa che poiché abbiamo passato un'attività a ExecutorService , è stato creato un thread per eseguirla e quel thread continua a esistere anche dopo che l'attività è stata completata.

Arresto a ExecutorService

Di conseguenza, dobbiamo "chiudere" (o arrestare) ExecutorService . Possiamo farlo in due modi:

  1. void shutdown() — dopo che questo metodo è stato chiamato, ExecutorService smette di accettare nuovi lavori. Tutte le attività precedentemente inviate a ExecutorService continueranno a essere eseguite.

    
    public static void main(String[] args) throws Exception {
    ExecutorService service = Executors.newFixedThreadPool(3);
        	service.submit(() -> System.out.println("task 1"));
        	service.submit(() -> System.out.println("task 2"));
        	service.shutdown();
        	// A RejectedExecutionException will occur here
        	service.submit(() -> System.out.println("task 3"));
    }
    
  2. List<Runnable> shutdownNow() — Questo metodo tenta di arrestare i processi attualmente attivi. Le attività che sono ancora in attesa del proprio turno vengono scartate e restituite come un elenco di elementi eseguibili .

    
    public static void main(String[] args) throws Exception {
        ExecutorService service = Executors.newFixedThreadPool(5);
        List.of(1, 2, 3, 4, 5, 6, 7, 8).forEach(i -> service.submit(() -> System.out.println(i)));
        List<Runnable> runnables = service.shutdownNow();
        runnables.forEach(System.out::println);
    }
    

Produzione:

1
2
4
3
java.util.concurrent.FutureTask@1e80bfe8[Attività non completata = java.util.concurrent.Executors$RunnableAdapter@4edde6e5[Attività racchiusa = Test$$Lambda$16/0x0000000800b95040@70177ecd]]
java.util.concurrent .FutureTask@cc34f4d[Non completata, attività = java.util.concurrent.Executors$RunnableAdapter@66a29884[Attività racchiusa = Test$$Lambda$16/0x0000000800b95040@4769b07b]]
java.util.concurrent.FutureTask@6f539caf[Non completata, attività = java.util.concurrent.Executors$RunnableAdapter@17a7cec2[Wrapped task = Test$$Lambda$16/0x0000000800b95040@65b3120a]]
5

Processo terminato con codice di uscita 0

L'output sarà diverso da un'esecuzione all'altra. Ci sono 2 tipi di linee nell'output:

  • Un numero indica che l' ExecutorService è riuscito a elaborare l'attività corrispondente, visualizzando il numero dall'elenco utilizzato per creare le attività.

  • I risultati della chiamata al metodo toString() su un oggetto FutureTask . Questi oggetti sono le attività inviate a ExecutorService ma non elaborate.

L'output ha un'altra sfumatura interessante. In un mondo ideale, vedremmo prima tutti i numeri visualizzati, seguiti dagli oggetti FutureTask . Ma i problemi di sincronizzazione confondono le righe nell'output.

Altri metodi

ExecutorService ha molti altri metodi relativi all'arresto:

  1. boolean waitTermination(long timeout, TimeUnit unit) — questo metodo blocca il thread che lo chiama. Il blocco termina non appena si verifica uno dei seguenti tre eventi:

    • dopo che il metodo shutdown() è stato chiamato, tutti i lavori attivi e tutte le attività pianificate sono state eseguite;
    • è trascorso il timeout determinato dai parametri del metodo;
    • il thread che ha chiamato il metodo awaitTermination() viene terminato.

    Il metodo restituisce true se ExecutorService viene arrestato prima della scadenza del timeout e false se il timeout è già trascorso.

    
    public static void main(String[] args) throws Exception {
    	ExecutorService service = Executors.newFixedThreadPool(2);
    	service.submit(() -> System.out.println("task 1"));
    	service.submit(() -> System.out.println("task 2"));
    	service.submit(() -> System.out.println("task 3"));
    	service.shutdown();
    	System.out.println(service.awaitTermination(1, TimeUnit.MICROSECONDS));
    }
    
  2. boolean isShutdown() — Restituisce true se il metodo shutdown() o shutdownNow() è stato chiamato su ExecutorService .

    
    public static void main(String[] args) throws Exception {
    	ExecutorService service = Executors.newFixedThreadPool(2);
    	service.submit(() -> System.out.println("task 1"));
    	service.submit(() -> System.out.println("task 2"));
    	service.submit(() -> System.out.println("task 3"));
    	System.out.println(service.isShutdown());
    	service.shutdown();
    	System.out.println(service.isShutdown());
    }
    
  3. boolean isTerminated() — Restituisce true se il metodo shutdown() o shutdownNow() è stato chiamato su ExecutorService e tutte le attività sono state eseguite.

    
    public static void main(String[] args) throws Exception {
        ExecutorService service = Executors.newFixedThreadPool(5);
        List.of(1, 2, 3, 4, 5, 6, 7, 8).forEach(i -> service.submit(() -> System.out.println(i)));
        service.shutdownNow();
        System.out.println(service.isTerminated());
    }
    

Esempio di codice che utilizza questi metodi:


public static void main(String[] args) throws Exception {
   ExecutorService service = Executors.newFixedThreadPool(16);
   Callable<String> task = () -> {
       Thread.sleep(1);
       return "Done";
   };
 
   // Add 10,000 tasks to the queue
   List<Future<String>> futures = IntStream.range(0, 10_000)
           .mapToObj(i -> service.submit(task))
           .collect(Collectors.toList());
   System.out.printf("%d tasks were submitted for execution.%n", futures.size());
 
   // Attempt to shut down
   service.shutdown();
   // Wait 100 milliseconds to finish the work
   if (service.awaitTermination(100, TimeUnit.MILLISECONDS)) {
       System.out.println("All tasks completed!");
   } else {
       // Stop forcibly
       List<Runnable> notExecuted = service.shutdownNow();
       System.out.printf("%d tasks were not started.%n", notExecuted.size());
   }
 
   System.out.printf("Total tasks completed: %d.%n", futures.stream().filter(Future::isDone).count());
}

Output (diverso da esecuzione a esecuzione):

10.000 compiti sono stati inviati per l'esecuzione.
9170 attività non sono state avviate.
Compiti totali completati: 830 compiti.

Processo terminato con codice di uscita 0