Executors în Java

Clasa cu metode statice care creează executori: cu număr fix de fire, cu un singur fir, cu fire virtuale sau programate.

Sintaxă
import java.util.concurrent.Executors;

static ExecutorService newFixedThreadPool(int nFire)
static ExecutorService newSingleThreadExecutor()
static ExecutorService newCachedThreadPool()

Ce face Executors

Executors este o „fabrică” de executori. Nu creezi obiecte Executors; folosești direct metodele ei statice, care returnează un ExecutorService gata configurat.

newFixedThreadPool(n) creează exact n fire care se refolosesc. Dacă trimiți mai multe sarcini decât fire, cele în plus stau la coadă. Este alegerea obișnuită pentru calcule: n egal cu numărul de procesoare (Runtime.getRuntime().availableProcessors()).

newSingleThreadExecutor() are un singur fir, deci sarcinile rulează pe rând, în ordinea trimiterii. Este util când sarcinile nu au voie să ruleze în același timp (de exemplu scrierea într-un fișier de jurnal). newCachedThreadPool() creează fire noi după nevoie și le închide pe cele nefolosite 60 de secunde.

Parametri

Parametru Descriere
nFire Numărul de fire pentru newFixedThreadPool; trebuie să fie cel puțin 1.

Ce returnează

Un ExecutorService nou.

Exemple

Exemplul 1: Un singur fir: sarcinile rulează în ordine

Cu un singur fir, a doua sarcină pornește doar după ce se termină prima, deci ordinea este mereu aceeași.

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

public class Main {
    public static void main(String[] args) {
        try (ExecutorService jurnal = Executors.newSingleThreadExecutor()) {
            for (String mesaj : new String[] {"Ana s-a autentificat", "Ana a plătit 59 lei", "Ana s-a deconectat"}) {
                jurnal.submit(() -> System.out.println("[jurnal] " + mesaj));
            }
        }
    }
}

Rezultat

[jurnal] Ana s-a autentificat
[jurnal] Ana a plătit 59 lei
[jurnal] Ana s-a deconectat

Exemplul 2: Câte sarcini rulează odată cu 2 fire

Șase sarcini a câte 200 ms pe 2 fire au nevoie de trei „runde”, deci aproximativ 600 ms. Contorul arată că niciodată nu rulează mai mult de 2 odată.

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

public class Main {
    public static void main(String[] args) {
        AtomicInteger active = new AtomicInteger();
        AtomicInteger maxim = new AtomicInteger();
        try (ExecutorService executor = Executors.newFixedThreadPool(2)) {
            for (int i = 0; i < 6; i++) {
                executor.submit(() -> {
                    maxim.accumulateAndGet(active.incrementAndGet(), Math::max);
                    Thread.sleep(200);
                    active.decrementAndGet();
                    return null;
                });
            }
        }
        System.out.println("Cel mai mare număr de sarcini simultane: " + maxim.get());
    }
}

Rezultat

Cel mai mare număr de sarcini simultane: 2

Exemplul 3: Numele firelor din grup

Ce fir primește fiecare sarcină depinde de sistem, deci ordinea diferă de la o rulare la alta.

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

public class Main {
    public static void main(String[] args) {
        try (ExecutorService executor = Executors.newFixedThreadPool(3)) {
            for (int i = 1; i <= 4; i++) {
                int nr = i;
                executor.submit(() -> System.out.println("Comanda " + nr + " pe " + Thread.currentThread().getName()));
            }
        }
    }
}

Rezultat posibil (diferă la fiecare rulare)

Comanda 1 pe pool-1-thread-1
Comanda 3 pe pool-1-thread-3
Comanda 2 pe pool-1-thread-2
Comanda 4 pe pool-1-thread-1

Greșeli frecvente

  • Creezi un executor nou pentru fiecare sarcină. Crearea firelor costă; creează executorul o dată și trimite-i toate sarcinile.
  • Folosești newCachedThreadPool() pentru mii de sarcini lente. Poate crea mii de fire de sistem. Pentru multe sarcini care așteaptă (rețea, fișiere), folosește fire virtuale.

Comenzi înrudite

  • ExecutorService: 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().
  • Executors.newVirtualThreadPerTaskExecutor: Creează un executor care pornește un fir virtual nou pentru fiecare sarcină; potrivit pentru mii de sarcini care așteaptă.
  • Executors.newScheduledThreadPool: Creează un executor care rulează sarcini după o întârziere sau periodic (la fiecare N secunde).
  • ExecutorService.submit: Trimite o sarcină executorului și returnează imediat un Future din care iei mai târziu rezultatul.