Помислете за проста програма:


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

Изпълнението на програмата произвежда конзолния изход, който очакваме:

Свършен

Но това не е последвано от изхода, който обикновено виждаме в IntelliJ IDEA:

Процесът завърши с изходен code 0

Обикновено виждаме това, когато програмата приключи.

защо става така

Описанието на метода newFixedThreadPool() ни казва, че нишките, създадени с помощта на ExecutorService, продължават да съществуват, докато не бъдат изрично спрени. Това означава, че тъй като сме предали задача на ExecutorService , е създадена нишка за нейното изпълнение и тази нишка продължава да съществува дори след като задачата е изпълнена.

Спиране в ExecutorService

В резултат на това трябва да "изключим" (or да спрем) ExecutorService . Можем да направим това по два начина:

  1. void shutdown() — след като този метод бъде извикан, ExecutorService спира да приема нови задачи. Всички задачи, изпратени преди това до ExecutorService, ще продължат да се изпълняват.

    
    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() — Този метод се опитва да спре задания, които са активни в момента. Задачите, които все още чакат своя ред, се отхвърлят и се връщат като списък с 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);
    }
    

Изход:

1
2
4
3
java.util.concurrent.futuretask@1e80bfe8 [не е изпълнен, задача = java.util.concurrent.executors$runnableadapter@4edde6e5 [увита задача = тест $$ lambda $ 16/0x0000000800b95040@701777ecd]]
java.Util.concurrent .FutureTask@cc34f4d[Не е завършена, задача = java.util.concurrent.Executors$RunnableAdapter@66a29884[Опакована задача = Test$$Lambda$16/0x0000000800b95040@4769b07b]]
java.util.concurrent.FutureTask@6f539caf [Не е изпълнено, задача = java.util.concurrent.Executors$RunnableAdapter@17a7cec2[Обвита задача = Test$$Lambda$16/0x0000000800b95040@65b3120a]]
5

Процесът завърши с изходен code 0

Резултатът ще се различава от цикъл до цикъл. Има 2 вида линии в изхода:

  • Число означава, че ExecutorService е успяла да обработи съответната задача, показвайки номера от списъка, който сме използвали за създаване на задачи.

  • Резултатите от извикването на метода toString() на обект FutureTask . Тези обекти са задачите, които са изпратени до ExecutorService , но не са обработени.

Резултатът има още един интересен нюанс. В един идеален свят първо ще видим всички показани числа, последвани от обектите FutureTask . Но проблемите със синхронизацията смесват редовете в изхода.

Други методи

ExecutorService има още няколко метода, свързани с спирането му:

  1. boolean awaitTermination(long timeout, TimeUnit unit) — този метод блокира нишката, която го извиква. Блокирането приключва веднага щом настъпи някое от следните три събития:

    • след извикване на метода shutdown() , всички активни задачи и всички планирани задачи са изпълнени;
    • времето за изчакване, определено от параметрите на метода, е изтекло;
    • нишката, която е извикала метода awaitTermination(), е прекратена.

    Методът връща true , ако ExecutorService е спрян преди изтичане на времето за изчакване, и false , ако времето за изчакване вече е изтекло.

    
    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() — Връща true , ако методът shutdown() or shutdownNow() е бил извикан на 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() — Връща true , ако методът shutdown() or shutdownNow() е бил извикан на ExecutorService и всички задачи са изпълнени.

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

Примерен code, който използва тези методи:


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

Резултат (различава се от изпълнение до изпълнение):

Подадени са за изпълнение 10 000 задачи.
9170 задачи не са стартирани.
Общо изпълнени задачи: 830 задачи.

Процесът завърши с изходен code 0