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

Po uruchomieniu programu otrzymujemy oczekiwane wyjście w konsoli:

zrobione

Ale wtedy nie widzimy zwykłego wyjścia dla IntellijIDEA:

Proces zakończony kodem wyjścia 0

Zwykle jest to widoczne na końcu wykonywania programu.

Dlaczego to się dzieje?

Z opisu metody newFixedThreadPool() możemy dowiedzieć się, że wątki utworzone za pomocą tej usługi ExecutorService będą nadal istnieć, dopóki nie zostaną jawnie zatrzymane. Dlatego, ponieważ przekazaliśmy jedno zadanie do ExecutorService , został utworzony wątek do jego wykonania, który istniał nawet po zakończeniu zadania.

Zatrzymaj ExecutorService

Tak więc ExecutorService musi zostać „zamknięty” (zatrzymany) po sobie. Możesz to zrobić na dwa sposoby:

  1. void shutdown() — po wywołaniu tej metody ExecutorService nie przyjmuje już nowych zadań. Wszystkie zadania, które zostały wcześniej przekazane do usługi ExecutorService, będą nadal działać.

    
    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() — Metoda próbuje zatrzymać aktualnie aktywne zadania. Zadania, które czekały na swoją kolej, są odrzucane i zwracane jako lista Runnable .

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

Wyjście programu:

1
2
4
3
java.util.concurrent.FutureTask@1e80bfe8[Nie ukończono, zadanie = java.util.concurrent.Executors$RunnableAdapter@4edde6e5[Opakowane zadanie = Test$$Lambda$16/0x0000000800b95040@70177ecd]]
java.util.concurrent java.util.concurrent.FutureTask@6f539caf
[Nie ukończono, zadanie = java.util.concurrent.Executors$RunnableAdapter@17a7cec2[Opakowane zadanie = Test$$Lambda$16/0x0000000800b95040@65b3120a]]]
5

Proces zakończony z kodem zakończenia 0

Dane wyjściowe będą się różnić w zależności od uruchomienia. Na wyjściu znajdują się 2 rodzaje linii:

  • numer - oznacza to, że to zadanie udało się obsłużyć przez ExecutorService i wyświetlił się numer z listy, z której stworzyliśmy zadania.

  • obiekt typu FutureTask , po wywołaniu jego metody toString() . Są to zadania, które zostały przesłane do usługi ExecutorService do wykonania, ale nie zostały przetworzone.

W tej konkluzji jest jeszcze jeden interesujący niuans. Gdybyśmy żyli w idealnym świecie, to najpierw byłyby wyświetlane wszystkie liczby, a następnie obiekty typu FutureTask . Ale z powodu problemów z synchronizacją wiersze na wyjściu są pomieszane.

Inne metody

Dodatkowo ExecutorService posiada kilka innych metod związanych z jego zatrzymaniem:

  1. boolean awaitTermination(long timeout, TimeUnit unit) - metoda blokuje wątek, który ją wywołał. Zamek zostaje złamany, gdy tylko nastąpi jedno z trzech zdarzeń:

    • po wywołaniu metody shutdown() wszystkie zadania aktywne i wszystkie zadania z kolejki zostały wykonane;
    • upłynął limit czasu, którego czas trwania jest określony przez parametry metody;
    • wątek, który wywołał metodę awaitTermination() został zakończony.

    Metoda zwraca true , jeśli usługa ExecutorService została zatrzymana przed upływem limitu czasu, i false , jeśli limit czasu upłynął wcześniej.

    
    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() — zwraca wartość true , jeśli usługa ExecutorService miała swoją metodę shutdown() lub shutdownNow() o nazwie .

    
    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() — zwraca wartość true , jeśli wywołano metodę shutdown() lub shutdownNow() usługi ExecutorService i wszystkie zadania zostały zakończone.

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

Przykładowy kod wykorzystujący omówione metody:


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

Wyjście programu (różne od uruchomienia do uruchomienia):

Do realizacji zgłoszono 10 000 zadań.
Więc zadania 9170 nie zostały uruchomione.
Łącznie wykonano 830 zadań.

Proces zakończony kodem wyjścia 0