Luați în considerare un program simplu:


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

Rularea programului produce rezultatul consolei pe care îl așteptăm:

Terminat

Dar aceasta nu este urmată de rezultatul pe care îl vedem de obicei în IntelliJ IDEA:

Procesul s-a încheiat cu codul de ieșire 0

De obicei vedem asta atunci când un program se termină.

De ce se întâmplă asta?

Descrierea metodei newFixedThreadPool() ne spune că firele create folosind un ExecutorService continuă să existe până când sunt oprite în mod explicit. Asta înseamnă că, deoarece am transmis o sarcină ExecutorService , un fir de execuție a fost creat pentru a o executa și acel fir de execuție continuă să existe chiar și după ce sarcina este finalizată.

Oprire la ExecutorService

Ca rezultat, trebuie să „închidem” (sau să oprim) ExecutorService . Putem face acest lucru în două moduri:

  1. void shutdown() — după ce această metodă este apelată, ExecutorService încetează să accepte noi locuri de muncă. Toate sarcinile trimise anterior ExecutorService vor continua să ruleze.

    
    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() — Această metodă încearcă să oprească joburile care sunt active în prezent. Sarcinile care încă își așteaptă rândul sunt aruncate și returnate ca o listă de 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);
    }
    

Ieșire:

1
2
4
3
java.util.concurrent.FutureTask@1e80bfe8[Nefinalizată, sarcină = java.util.concurrent.Executors$RunnableAdapter@4edde6e5[Sarcina înfășurată = Test$$Lambda$16/0x0000000800b700800b9500b950
. .FutureTask@cc34f4d[Nefinalizat, task = java.util.concurrent.Executors$RunnableAdapter@66a29884[Wrapped task = Test$$Lambda$16/0x0000000800b95040@4769b07b.]con complets@current java6. ed
, sarcină = java.util.concurrent.Executors$RunnableAdapter@17a7cec2[Wrapped task = Test$$Lambda$16/0x0000000800b95040@65b3120a]]
5

Procesul încheiat cu codul de ieșire 0

Ieșirea va diferi de la o rulare la alta. Există 2 tipuri de linii în ieșire:

  • Un număr înseamnă că ExecutorService a reușit să proceseze sarcina corespunzătoare, afișând numărul din lista pe care am folosit-o pentru a crea sarcini.

  • Rezultatele apelării metodei toString() pe un obiect FutureTask . Aceste obiecte sunt sarcinile care au fost transmise ExecutorService, dar nu au fost procesate.

Ieșirea are o altă nuanță interesantă. Într-o lume ideală, am vedea mai întâi toate numerele afișate, urmate de obiectele FutureTask . Dar problemele de sincronizare amestecă liniile din ieșire.

Alte metode

ExecutorService are mai multe metode legate de oprirea acestuia:

  1. boolean awaitTermination (timeout lung, unitate TimeUnit) — această metodă blochează firul care îl apelează. Blocul se termină de îndată ce are loc oricare dintre următoarele trei evenimente:

    • după ce metoda shutdown() este apelată, toate joburile active și toate sarcinile programate au fost executate;
    • a expirat timpul de expirare determinat de parametrii metodei;
    • firul care a apelat metoda awaitTermination() este terminat.

    Metoda returnează true dacă ExecutorService este oprit înainte de expirarea timpului, și false dacă timeout-ul a trecut deja.

    
    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() — Returnează adevărat dacă metoda shutdown() sau shutdownNow() a fost apelată pe 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() — Returnează adevărat dacă metoda shutdown() sau shutdownNow() a fost apelată pe ExecutorService și toate sarcinile sunt efectuate.

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

Exemplu de cod care folosește aceste metode:


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

Ieșire (diferă de la rulare la rulare):

Au fost depuse spre executare 10.000 de sarcini.
9170 de sarcini nu au fost începute.
Total sarcini finalizate: 830 sarcini.

Procesul s-a încheiat cu codul de ieșire 0