0Pricing
Java Academy · Lekcja

ExecutorService i typy pul wątków

Twórz stałe, buforowane, jednowątkowe i planowane pule wątków oraz poznaj sytuacje, w których należy używać każdej z nich.

ExecutorService i typy pul wątków to bezpłatna lekcja Java Academy na CoddyKit. To lekcja 1 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Java Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Java Academy zawiera 4 lekcji w sumie.

Dlaczego używać puli wątków?

Tworzenie nowego Thread dla każdego zadania jest kosztowne — tworzenie i niszczenie wątków wiąże się z dodatkowym narzutem. Pula wątków ponownie wykorzystuje stały zestaw wątków do wykonywania wielu zadań, zmniejszając narzut i kontrolując zużycie zasobów.

import java.util.concurrent.*;

// Bad practice: new thread per task
new Thread(() -> System.out.println("task")).start();

// Better: reuse threads from a pool
ExecutorService pool = Executors.newFixedThreadPool(4);
pool.submit(() -> System.out.println("task from pool"));
pool.shutdown();

newFixedThreadPool

Tworzy pulę z dokładnie N wątkami. Dodatkowe zadania trafiają do kolejki do czasu, aż wątek stanie się dostępny. Dobre rozwiązanie, gdy znają Państwo pożądany poziom równoległości.

ExecutorService pool = Executors.newFixedThreadPool(4);

for (int i = 0; i < 10; i++) {
    final int taskId = i;
    pool.submit(() -> {
        System.out.println("Task " + taskId + " on " + Thread.currentThread().getName());
    });
}

pool.shutdown();
pool.awaitTermination(10, TimeUnit.SECONDS);

newCachedThreadPool

Tworzy wątki na żądanie i ponownie wykorzystuje bezczynne wątki. Wątki bezczynne przez 60 sekund są kończone. Dobre rozwiązanie dla wielu krótkotrwałych zadań, ale ryzykowne w przypadku zadań długotrwałych lub nieograniczonej ich liczby (może doprowadzić do wyczerpania pamięci).

ExecutorService cached = Executors.newCachedThreadPool();

for (int i = 0; i < 100; i++) {
    cached.submit(() -> {
        // quick task
        double result = Math.sqrt(Math.random());
    });
}
cached.shutdown();

newSingleThreadExecutor

Pula zawierająca dokładnie jeden wątek. Zadania są wykonywane sekwencyjnie, w kolejności ich przesłania. Dobre rozwiązanie do szeregowania dostępu do współdzielonego zasobu.

ExecutorService single = Executors.newSingleThreadExecutor();

single.submit(() -> System.out.println("Task 1"));
single.submit(() -> System.out.println("Task 2"));
single.submit(() -> System.out.println("Task 3"));
// Always prints: Task 1, Task 2, Task 3 (sequential)
single.shutdown();

newScheduledThreadPool

Obsługuje zadania zaplanowane i cykliczne. Należy go używać do odpytywania, ponawiania prób i konserwacji w tle.

ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);

// One-shot delay
scheduler.schedule(() -> System.out.println("Delayed!"),
    3, TimeUnit.SECONDS);

// Fixed rate (every 2 seconds)
scheduler.scheduleAtFixedRate(
    () -> System.out.println("Tick"),
    0, 2, TimeUnit.SECONDS);

// Run later: scheduler.shutdown();

VirtualThreadExecutor (Java 21+)

Java 21 wprowadza wątki wirtualne — lekkie i tanie w tworzeniu. W przypadku obciążeń intensywnie korzystających z operacji wejścia-wyjścia użyj Executors.newVirtualThreadPerTaskExecutor():

// Java 21+
try (var exec = Executors.newVirtualThreadPerTaskExecutor()) {
    for (int i = 0; i < 10_000; i++) {
        final int id = i;
        exec.submit(() -> {
            Thread.sleep(100); // blocks without tying up OS thread
            System.out.println("vthread " + id);
        });
    }
} // auto-shutdown

Wybór odpowiedniej puli

Wskazówki:

  • Zadania związane z CPU: pula stała z N = liczbą rdzeni procesora
  • Zadania związane z operacjami wejścia-wyjścia: pula buforowana lub wątki wirtualne
  • Zadania sekwencyjne: executor z jednym wątkiem
  • Zadania zaplanowane/cykliczne: pula zaplanowanych wątków
int cores = Runtime.getRuntime().availableProcessors();
ExecutorService cpuPool = Executors.newFixedThreadPool(cores);
System.out.println("Pool size: " + cores);

Niestandardowy ThreadPoolExecutor

Aby uzyskać szczegółową kontrolę, utwórz bezpośrednio obiekt ThreadPoolExecutor:

ThreadPoolExecutor tpe = new ThreadPoolExecutor(
    2,           // corePoolSize
    10,          // maximumPoolSize
    60L,         // keepAliveTime
    TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(100), // work queue
    Executors.defaultThreadFactory(),
    new ThreadPoolExecutor.CallerRunsPolicy() // rejection policy
);
tpe.submit(() -> System.out.println("Custom pool task"));
tpe.shutdown();

Kontrolowane zamykanie

Zawsze wyłączaj ExecutorService, aby zwolnić wątki. shutdown() przestaje przyjmować nowe zadania, ale kończy te znajdujące się w kolejce. shutdownNow() próbuje anulować wykonywane zadania.

ExecutorService pool = Executors.newFixedThreadPool(4);
// ... submit tasks ...

pool.shutdown(); // no new tasks
try {
    if (!pool.awaitTermination(30, TimeUnit.SECONDS)) {
        pool.shutdownNow(); // force cancel
    }
} catch (InterruptedException e) {
    pool.shutdownNow();
    Thread.currentThread().interrupt();
}

ExecutorService jako AutoCloseable (Java 19+)

W Javie 19 i nowszych ExecutorService implementuje AutoCloseable. Użyj try-with-resources, aby automatycznie zamknąć pulę:

// Java 19+ (preview), Java 21 stable
try (ExecutorService pool = Executors.newFixedThreadPool(4)) {
    pool.submit(() -> System.out.println("task"));
} // pool.close() called: waits for tasks then shuts down

Zasady odrzucania zadań

Gdy pula jest nasycona (wszystkie wątki są zajęte, a kolejka pełna), zasada odrzucania obsługuje nadmiarowe zadania:

  • AbortPolicy (domyślna): zgłasza RejectedExecutionException
  • CallerRunsPolicy: wątek wywołujący wykonuje zadanie
  • DiscardPolicy: po cichu odrzuca zadanie
  • DiscardOldestPolicy: odrzuca najstarsze zadanie z kolejki

Szybkie sprawdzenie

Mają Państwo zadanie związane z CPU, które wykonuje intensywne obliczenia. Ile wątków powinna mieć stała pula wątków?

Podsumowanie: ExecutorService i pule wątków

Najważniejsze informacje:

  • Pula stała: stała liczba N wątków; dodatkowe zadania trafiają do kolejki
  • Pula buforowana: zwiększa się na żądanie; bezczynne wątki wygasają po 60 sekundach
  • Jeden wątek: gwarantowana kolejność wykonywania sekwencyjnego
  • Pula zaplanowana: zadania opóźnione i cykliczne
  • Zawsze wywołuj shutdown(); użyj awaitTermination() do kontrolowanego zakończenia

Często zadawane pytania

Czy lekcja „ExecutorService i typy pul wątków” jest bezpłatna?

Tak — pełny tekst „ExecutorService i typy pul wątków” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Java Academy, przejdź na CoddyKit PRO. Kurs Java Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „ExecutorService i typy pul wątków”?

Twórz stałe, buforowane, jednowątkowe i planowane pule wątków oraz poznaj sytuacje, w których należy używać każdej z nich. Ćwiczysz Java Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć Java Academy?

Nie wymagamy żadnego doświadczenia. Java Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 1 z 4.

Ile czasu zajmuje lekcja „ExecutorService i typy pul wątków”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji Java Academy?

Tak. Każda lekcja Java Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. ExecutorService i typy pul wątków
  2. Przesyłanie zadań: Runnable a Callable
  3. Future i obsługa błędów
  4. ScheduledExecutorService do zadań cyklicznych
← Powrót do Java Academy