Betrachten Sie ein einfaches Programm:


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

Das Ausführen des Programms erzeugt die erwartete Konsolenausgabe:

Erledigt

Darauf folgt jedoch nicht die Ausgabe, die wir normalerweise in IntelliJ IDEA sehen:

Prozess mit Exit-Code 0 beendet

Normalerweise sehen wir das, wenn ein Programm endet.

Warum passiert das?

Die Beschreibung der Methode newFixedThreadPool() sagt uns, dass Threads, die mit einem ExecutorService erstellt wurden , so lange bestehen bleiben, bis sie explizit gestoppt werden. Das bedeutet, dass, weil wir eine Aufgabe an den ExecutorService übergeben haben , ein Thread erstellt wurde, um sie auszuführen, und dass dieser Thread auch nach Abschluss der Aufgabe weiterhin existiert.

Stoppen bei ExecutorService

Daher müssen wir den ExecutorService „herunterfahren“ (oder stoppen) . Wir können dies auf zwei Arten tun:

  1. void Shutdown() – nachdem diese Methode aufgerufen wurde, nimmt der ExecutorService keine neuen Jobs mehr an. Alle zuvor an den ExecutorService übermittelten Aufgaben werden weiterhin ausgeführt.

    
    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() – Diese Methode versucht, Jobs zu stoppen, die derzeit aktiv sind. Aufgaben, die noch darauf warten, an die Reihe zu kommen, werden verworfen und als Liste von Runnables zurückgegeben .

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

Ausgang:

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

Prozess mit Exit-Code 0 abgeschlossen

Die Ausgabe unterscheidet sich von Lauf zu Lauf. Es gibt zwei Arten von Zeilen in der Ausgabe:

  • Eine Zahl bedeutet, dass es dem ExecutorService gelungen ist, die entsprechende Aufgabe zu verarbeiten, und zeigt die Zahl aus der Liste an, die wir zum Erstellen von Aufgaben verwendet haben.

  • Die Ergebnisse des Aufrufs der toString()- Methode für ein FutureTask- Objekt. Bei diesen Objekten handelt es sich um Aufgaben, die an den ExecutorService übermittelt , aber nicht verarbeitet wurden.

Die Ausgabe hat eine weitere interessante Nuance. In einer idealen Welt würden wir zuerst alle angezeigten Zahlen sehen, gefolgt von den FutureTask- Objekten. Aber Synchronisationsprobleme bringen die Zeilen in der Ausgabe durcheinander.

Andere Methoden

ExecutorService verfügt über mehrere weitere Methoden zum Stoppen:

  1. boolean waitingTermination(long timeout, TimeUnit-Einheit) – diese Methode blockiert den Thread, der sie aufruft. Die Sperre endet, sobald eines der folgenden drei Ereignisse eintritt:

    • Nachdem die Methode „shutdown()“ aufgerufen wurde, wurden alle aktiven Jobs und alle geplanten Aufgaben ausgeführt.
    • das durch die Methodenparameter bestimmte Timeout ist abgelaufen;
    • Der Thread, der die Methode „awaitTermination()“ aufgerufen hat , wird beendet.

    Die Methode gibt true zurück , wenn der ExecutorService vor Ablauf des Timeouts gestoppt wird, und false , wenn das Timeout bereits abgelaufen ist.

    
    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() – Gibt „ true“ zurück , wenn die Methode „shutdown()“ oder „shutdownNow()“ für den ExecutorService aufgerufen wurde .

    
    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() – Gibt „ true“ zurück , wenn die Methode „shutdown()“ oder „shutdownNow()“ für den ExecutorService aufgerufen wurde und alle Aufgaben erledigt sind.

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

Beispielcode, der diese Methoden verwendet:


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

Ausgabe (von Lauf zu Lauf unterschiedlich):

10.000 Aufgaben wurden zur Ausführung eingereicht.
9170 Aufgaben wurden nicht gestartet.
Insgesamt erledigte Aufgaben: 830 Aufgaben.

Prozess mit Exit-Code 0 beendet