Tenk på et 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"));
}

Å kjøre programmet produserer konsollutgangen vi forventer:

ferdig

Men dette blir ikke fulgt av utdataene som vi vanligvis ser i IntelliJ IDEA:

Prosessen avsluttet med utgangskode 0

Det ser vi vanligvis når et program avsluttes.

Hvorfor skjer det?

Beskrivelsen av metoden newFixedThreadPool() forteller oss at tråder opprettet ved hjelp av en ExecutorService fortsetter å eksistere til de eksplisitt stoppes. Det betyr at fordi vi sendte en oppgave til ExecutorService , ble det opprettet en tråd for å utføre den, og den tråden fortsetter å eksistere selv etter at oppgaven er fullført.

Stopper hos ExecutorService

Som et resultat må vi "slå av" (eller stoppe) ExecutorService . Vi kan gjøre dette på to måter:

  1. void shutdown() — etter at denne metoden er kalt, slutter ExecutorService å godta nye jobber. Alle oppgaver som tidligere er sendt inn til ExecutorService vil fortsette å kjø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 metoden prøver å stoppe jobber som er aktive. Oppgaver som fortsatt venter på tur blir forkastet og returnert 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);
    }
    

Produksjon:

1
2
4
3
java.util.concurrent.FutureTask@1e80bfe8[Ikke fullført, oppgave = java.util.concurrent.Executors$RunnableAdapter@4edde6e5[Wrapped task = Test$$Lambda$16/0x000000050700b]
jaconcurrent.70 .FutureTask@cc34f4d[Ikke fullført, oppgave = java.util.concurrent.Executors$RunnableAdapter@66a29884[Innpakket oppgave = Test$$Lambda$16/0x0000000800b95040@4769b07FutilT.concurrent@4769b07b]
[Ikke fullført, oppgave = java.util.concurrent.Executors$RunnableAdapter@17a7cec2[Wrapped task = Test$$Lambda$16/0x0000000800b95040@65b3120a]]
5

Prosessen fullført med utgangskode 0

Utgangen vil variere fra kjøring til kjøring. Det er 2 typer linjer i utgangen:

  • Et tall betyr at ExecutorService klarte å behandle den tilsvarende oppgaven, og viser nummeret fra listen vi brukte til å lage oppgaver.

  • Resultatene av å kalle toString() -metoden på et FutureTask- objekt. Disse objektene er oppgavene som ble sendt til ExecutorService , men som ikke ble behandlet.

Utgangen har en annen interessant nyanse. I en ideell verden vil vi først se alle de viste tallene, etterfulgt av FutureTask- objektene. Men synkroniseringsproblemer blander linjene i utdataene.

Andre metoder

ExecutorService har flere metoder relatert til å stoppe det:

  1. boolean awaitTermination (lang tidsavbrudd, TimeUnit-enhet) — denne metoden blokkerer tråden som kaller den. Blokken avsluttes så snart en av følgende tre hendelser inntreffer:

    • etter at shutdown()- metoden er kalt, har alle aktive jobber og alle planlagte oppgaver blitt utført;
    • tidsavbruddet bestemt av metodeparameterne har gått ut;
    • tråden som kalte awaitTermination()- metoden avsluttes.

    Metoden returnerer true hvis ExecutorService stoppes før tidsavbruddet utløper, og usant hvis tidsavbruddet allerede var utløpt.

    
    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 true hvis shutdown()- eller shutdownNow()- metoden har blitt kalt 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 true hvis shutdown()- eller shutdownNow()- metoden har blitt kalt på ExecutorService og alle oppgaver er utfø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 som bruker disse metodene:


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

Utgang (forskjellig fra kjøring til kjøring):

10 000 oppgaver ble levert til utførelse.
9170 oppgaver ble ikke startet.
Totalt utførte oppgaver: 830 oppgaver.

Prosessen avsluttet med utgangskode 0