Tänk på ett enkelt program:


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

Att köra programmet producerar den konsolutgång vi förväntar oss:

Gjort

Men detta följs inte av utdata som vi vanligtvis ser i IntelliJ IDEA:

Processen avslutad med utgångskod 0

Det brukar vi se när ett program slutar.

Varför händer det?

Beskrivningen av metoden newFixedThreadPool() talar om för oss att trådar skapade med en ExecutorService fortsätter att existera tills de explicit stoppas. Det betyder att eftersom vi skickade en uppgift till ExecutorService skapades en tråd för att köra den, och den tråden fortsätter att existera även efter att uppgiften är klar.

Slutar på ExecutorService

Som ett resultat måste vi "stänga av" (eller stoppa) ExecutorService . Vi kan göra detta på två sätt:

  1. void shutdown() — efter att denna metod har anropats slutar ExecutorService att acceptera nya jobb. Alla uppgifter som tidigare lämnats in till ExecutorService kommer att fortsätta att köras.

    
    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() — Denna metod försöker stoppa jobb som för närvarande är aktiva. Uppgifter som fortfarande väntar på sin tur kasseras och returneras som en lista över körbara .

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

Produktion:

Java
_
_
_
_
_ .FutureTask@cc34f4d[Ej slutförd, uppgift = java.util.concurrent.Executors$RunnableAdapter@66a29884[Omslagen uppgift = Test$$Lambda$16/0x0000000800b95040@4769b07b]
java.uFurT@4769b07b. [Ej klar, uppgift = java.util.concurrent.Executors$RunnableAdapter@17a7cec2[Wrapped task = Test$$Lambda$16/0x0000000800b95040@65b3120a]]
5

Processen avslutad med exitkod 0

Utdata kommer att skilja sig från körning till körning. Det finns 2 typer av linjer i utgången:

  • Ett nummer betyder att ExecutorService lyckades bearbeta motsvarande uppgift och visa numret från listan som vi använde för att skapa uppgifter.

  • Resultaten av att anropa metoden toString() på ett FutureTask- objekt. Dessa objekt är de uppgifter som lämnats till ExecutorService men som inte behandlats.

Utgången har en annan intressant nyans. I en idealisk värld skulle vi först se alla visade siffror, följt av FutureTask -objekten. Men synkroniseringsproblem rör ihop raderna i utgången.

Andra metoder

ExecutorService har flera fler metoder relaterade till att stoppa det:

  1. boolean awaitTermination (lång timeout, TimeUnit-enhet) — denna metod blockerar tråden som anropar den. Blockeringen slutar så snart någon av följande tre händelser inträffar:

    • efter att metoden shutdown() har anropats har alla aktiva jobb och alla schemalagda uppgifter utförts;
    • timeouten som bestäms av metodparametrarna har förflutit;
    • tråden som anropade metoden awaitTermination() avslutas.

    Metoden returnerar true om ExecutorService stoppas innan timeouten löper ut, och false om timeouten redan har förflutit.

    
    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() — Returnerar true om metoden shutdown() eller shutdownNow() har anropats på 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() — Returnerar sant om metoden shutdown() eller shutdownNow() har anropats på ExecutorService och alla uppgifter är gjorda.

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

Exempelkod som använder dessa metoder:


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

Utdata (skillnad från körning till körning):

10 000 uppgifter lämnades in för utförande.
9170 uppgifter startades inte.
Totalt slutförda uppgifter: 830 uppgifter.

Processen avslutad med utgångskod 0