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

Kørsel af programmet producerer det konsoloutput, vi forventer:

Færdig

Men dette efterfølges ikke af det output, som vi normalt ser i IntelliJ IDEA:

Processen afsluttet med udgangskode 0

Det ser vi normalt, når et program slutter.

Hvorfor sker det?

Beskrivelsen af ​​metoden newFixedThreadPool() fortæller os, at tråde oprettet ved hjælp af en ExecutorService fortsætter med at eksistere, indtil de eksplicit stoppes. Det betyder, at fordi vi sendte en opgave til ExecutorService , blev der oprettet en tråd for at udføre den, og den tråd fortsætter med at eksistere, selv efter opgaven er udført.

Stopper hos ExecutorService

Som et resultat er vi nødt til at "lukke" (eller stoppe) ExecutorService . Det kan vi gøre på to måder:

  1. void shutdown() — efter at denne metode er kaldt, stopper ExecutorService med at acceptere nye job. Alle opgaver, der tidligere er indsendt til ExecutorService , vil fortsætte med at køre.

    
    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() — Denne metode forsøger at stoppe job, der i øjeblikket er aktive. Opgaver, der stadig venter på deres tur, kasseres og returneres som en liste over 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);
    }
    

Produktion:

1
2
4
3
java.util.concurrent.FutureTask@1e80bfe8[Ikke fuldført, opgave = java.util.concurrent.Executors$RunnableAdapter@4edde6e5[Indpakket opgave = Test$$Lambda$16/0x00000000700b
. .FutureTask@cc34f4d[Ikke fuldført, opgave = java.util.concurrent.Executors$RunnableAdapter@66a29884[Indesluttet opgave = Test$$Lambda$16/0x0000000800b95040@4769b07f.
[Ikke afsluttet, opgave = java.util.concurrent.Executors$RunnableAdapter@17a7cec2[Wrapped task = Test$$Lambda$16/0x0000000800b95040@65b3120a]]
5

Processen afsluttet med exitkode 0

Outputtet vil variere fra kørsel til kørsel. Der er 2 slags linjer i outputtet:

  • Et tal betyder, at ExecutorService formåede at behandle den tilsvarende opgave ved at vise nummeret fra den liste, vi brugte til at oprette opgaver.

  • Resultaterne af at kalde toString() -metoden på et FutureTask- objekt. Disse objekter er de opgaver, der blev indsendt til ExecutorService , men som ikke blev behandlet.

Outputtet har en anden interessant nuance. I en ideel verden ville vi først se alle de viste tal, efterfulgt af FutureTask -objekterne. Men synkroniseringsproblemer blander linjerne i outputtet.

Andre metoder

ExecutorService har flere flere metoder relateret til at stoppe det:

  1. boolean awaitTermination (lang timeout, TimeUnit unit) — denne metode blokerer tråden, der kalder den. Blokken slutter, så snart en af ​​følgende tre hændelser indtræffer:

    • efter at shutdown() metoden er kaldt, er alle aktive job og alle planlagte opgaver blevet udført;
    • timeoutet bestemt af metodeparametrene er udløbet;
    • tråden, der kaldte awaitTermination()- metoden, afsluttes.

    Metoden returnerer true , hvis ExecutorService stoppes, før timeout udløber, og false , hvis timeout allerede var udløbet.

    
    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() — Returnerer sand , hvis shutdown()- eller shutdownNow()- metoden er blevet kaldt 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() — Returnerer sand , hvis metoden shutdown() eller shutdownNow() er blevet kaldt på ExecutorService , og alle opgaver er udført.

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

Eksempelkode, der bruger disse 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());
}

Output (forskelligt fra kørsel til kørsel):

10.000 opgaver blev sendt til udførelse.
9170 opgaver blev ikke startet.
Samlet udførte opgaver: 830 opgaver.

Processen afsluttet med udgangskode 0