ExecutorService în Java

Un grup de fire de execuție care primește sarcini și le rulează; se oprește cu shutdown() și se așteaptă cu awaitTermination().

Sintaxă
import java.util.concurrent.ExecutorService;

void shutdown()
List<Runnable> shutdownNow()
boolean awaitTermination(long timp, TimeUnit unitate) throws InterruptedException
boolean isShutdown()
boolean isTerminated()

Ce face ExecutorService

ExecutorService este o interfață pentru un „serviciu” care rulează sarcini pe fire de execuție (thread-uri). În loc să creezi singur câte un Thread pentru fiecare sarcină, trimiți sarcinile executorului, iar el le împarte pe firele lui. Creezi un executor cu metodele clasei Executors, de exemplu Executors.newFixedThreadPool(4).

Firele unui executor nu se opresc singure. Când nu mai ai sarcini de trimis, apelezi shutdown(): executorul nu mai primește sarcini noi, dar le termină pe cele deja primite. shutdown() nu așteaptă; revine imediat.

Pentru a aștepta terminarea sarcinilor apelezi awaitTermination(timp, unitate). Metoda blochează până când toate sarcinile se termină (returnează true) sau până expiră timpul (returnează false). shutdownNow() încearcă să întrerupă sarcinile care rulează și returnează lista celor care nu au pornit încă. Din Java 19 poți folosi executorul în try-with-resources: vezi ExecutorService.close.

Parametri

Parametru Descriere
timp Cât să aștepte cel mult awaitTermination.
unitate Unitatea pentru timp: TimeUnit.SECONDS, TimeUnit.MILLISECONDS etc.

Ce returnează

shutdown() nu returnează nimic. awaitTermination returnează true dacă toate sarcinile s-au terminat la timp, altfel false.

Exemple

Exemplul 1: Trei sarcini pe două fire, apoi oprirea executorului

Rezultatele se scriu într-un tablou, fiecare sarcină în poziția ei, deci ordinea afișării nu depinde de fire.

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        String[] orase = {"Iași", "Cluj", "Timișoara"};
        int[] lungimi = new int[orase.length];
        ExecutorService executor = Executors.newFixedThreadPool(2);
        for (int i = 0; i < orase.length; i++) {
            int poz = i;
            executor.execute(() -> lungimi[poz] = orase[poz].length());
        }
        executor.shutdown();
        boolean gata = executor.awaitTermination(5, TimeUnit.SECONDS);
        System.out.println("Terminat la timp: " + gata);
        for (int i = 0; i < orase.length; i++) {
            System.out.println(orase[i] + " are " + lungimi[i] + " litere");
        }
        System.out.println("isShutdown: " + executor.isShutdown() + ", isTerminated: " + executor.isTerminated());
    }
}

Rezultat

Terminat la timp: true
Iași are 4 litere
Cluj are 4 litere
Timișoara are 9 litere
isShutdown: true, isTerminated: true

Exemplul 2: awaitTermination cu timp prea scurt

Sarcina durează o secundă, dar așteptăm doar 100 ms: awaitTermination returnează false. Apoi shutdownNow() întrerupe sarcina.

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        executor.execute(() -> {
            try {
                Thread.sleep(1000);
                System.out.println("Raport generat");
            } catch (InterruptedException e) {
                System.out.println("Raportul a fost întrerupt");
            }
        });
        executor.shutdown();
        System.out.println("Gata în 100 ms? " + executor.awaitTermination(100, TimeUnit.MILLISECONDS));
        executor.shutdownNow();
        System.out.println("Gata după shutdownNow? " + executor.awaitTermination(1, TimeUnit.SECONDS));
    }
}

Rezultat

Gata în 100 ms? false
Raportul a fost întrerupt
Gata după shutdownNow? true

Exemplul 3: După shutdown nu mai primește sarcini

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;

public class Main {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(2);
        executor.shutdown();
        try {
            executor.execute(() -> System.out.println("nu se mai rulează"));
        } catch (RejectedExecutionException e) {
            System.out.println("Sarcina a fost respinsă: executorul este oprit");
        }
    }
}

Rezultat

Sarcina a fost respinsă: executorul este oprit

Greșeli frecvente

  • Uiți shutdown(). Firele unui newFixedThreadPool nu sunt fire daemon, deci programul nu se termină după main: rămâne „agățat”.
  • Crezi că shutdown() așteaptă sarcinile. Revine imediat; pentru așteptare folosește awaitTermination sau close().
  • Ignori rezultatul lui awaitTermination. Dacă este false, sarcinile încă rulează și rezultatele lor nu sunt gata.

Comenzi înrudite

  • Executors: Clasa cu metode statice care creează executori: cu număr fix de fire, cu un singur fir, cu fire virtuale sau programate.
  • ExecutorService.close: Oprește executorul și așteaptă să se termine toate sarcinile; permite folosirea executorului în try-with-resources.
  • ExecutorService.submit: Trimite o sarcină executorului și returnează imediat un Future din care iei mai târziu rezultatul.
  • Future: Rezultatul unei sarcini care rulează pe alt fir: get() așteaptă și îl returnează, isDone() spune dacă s-a terminat.
  • TimeUnit: Enumerarea unităților de timp (SECONDS, MINUTES…) folosită la așteptări; face conversii și pauze ușor de citit.