Overweeg een eenvoudig 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"));
}

Het uitvoeren van het programma produceert de console-uitvoer die we verwachten:

klaar

Maar dit wordt niet gevolgd door de uitvoer die we gewoonlijk zien in IntelliJ IDEA:

Proces beëindigd met afsluitcode 0

Dat zien we meestal als een programma eindigt.

Waarom gebeurt dat?

De beschrijving van de methode newFixedThreadPool() vertelt ons dat threads die met een ExecutorService zijn gemaakt , blijven bestaan ​​totdat ze expliciet worden gestopt. Dat betekent dat omdat we een taak hebben doorgegeven aan de ExecutorService , er een thread is gemaakt om deze uit te voeren, en die thread blijft bestaan, zelfs nadat de taak is voltooid.

Stoppen bij ExecutorService

Als gevolg hiervan moeten we de ExecutorService "afsluiten" (of stoppen) . Dit kunnen we op twee manieren doen:

  1. void shutdown() — nadat deze methode is aangeroepen, stopt de ExecutorService met het accepteren van nieuwe taken. Alle taken die eerder naar de ExecutorService zijn verzonden , blijven actief.

    
    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() — Deze methode probeert taken te stoppen die momenteel actief zijn. Taken die nog op hun beurt wachten, worden weggegooid en geretourneerd als een lijst met Runnables .

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

Uitgang:

1
2
4
3
java.util.concurrent.FutureTask@1e80bfe8[Niet voltooid, taak = java.util.concurrent.Executors$RunnableAdapter@4edde6e5[Ingepakte taak = Test$$Lambda$16/0x0000000800b95040@70177ecd]]
java.util.concurrent .FutureTask@cc34f4d[Niet voltooid, taak = java.util.concurrent.Executors$RunnableAdapter@66a29884[Verpakte taak = Test$$Lambda$16/0x0000000800b95040@4769b07b]]
java.util.concurrent.FutureTask@6f539caf[Niet voltooid, taak = java.util.concurrent.Executors$RunnableAdapter@17a7cec2[Wrapped task = Test$$Lambda$16/0x0000000800b95040@65b3120a]]
5

Proces voltooid met afsluitcode 0

De output zal verschillen van run tot run. Er zijn 2 soorten regels in de uitvoer:

  • Een nummer betekent dat de ExecutorService erin geslaagd is om de overeenkomstige taak te verwerken, waarbij het nummer wordt weergegeven uit de lijst die we hebben gebruikt om taken te maken.

  • De resultaten van het aanroepen van de methode toString() op een FutureTask- object. Deze objecten zijn de taken die zijn ingediend bij de ExecutorService maar niet zijn verwerkt.

De uitvoer heeft nog een interessante nuance. In een ideale wereld zouden we eerst alle weergegeven getallen zien, gevolgd door de FutureTask- objecten. Maar door synchronisatieproblemen lopen de lijnen in de uitvoer door elkaar.

Andere methodes

ExecutorService heeft nog een aantal methoden om het te stoppen:

  1. boolean waitTermination(long timeout, TimeUnit unit) — deze methode blokkeert de thread die het aanroept. De blokkering eindigt zodra een van de volgende drie gebeurtenissen zich voordoet:

    • nadat de methode shutdown() is aangeroepen, zijn alle actieve taken en alle geplande taken uitgevoerd;
    • de time-out bepaald door de methodeparameters is verstreken;
    • de thread die de methode waitTermination() heeft aangeroepen , wordt beëindigd.

    De methode retourneert true als de ExecutorService wordt gestopt voordat de time-out is verstreken, en false als de time-out al is verstreken.

    
    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() — Retourneert true als de methode shutdown() of shutdownNow() is aangeroepen op de 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() — Retourneert true als de methode shutdown() of shutdownNow() is aangeroepen op de ExecutorService en alle taken zijn uitgevoerd.

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

Voorbeeldcode die deze methoden gebruikt:


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

Uitvoer (verschilt van run tot run):

10.000 taken werden ingediend voor uitvoering.
9170 taken zijn niet gestart.
Totaal voltooide taken: 830 taken.

Proces beëindigd met afsluitcode 0