Zwykle tworząc aplikację wielowątkową mamy do czynienia z organizacją pracy wątków. Im większa nasza aplikacja i im więcej wątków potrzebujemy do zorganizowania wykonywania zadań wielowątkowych, tym więcej obiektównadający się do bieganiatworzymy.

Należy tutaj zaznaczyć, że utworzenie wątku w Javie jest dość kosztowną operacją. Jeśli będziemy tworzyć nową instancję wątku za każdym razem, aby wykonać operację, będziemy mieli duże problemy z wydajnością, a co za tym idzie, z kondycją aplikacji.

W tym miejscu z pomocą przychodzą pule wątków i ThreadPoolExecutor .

Pula wątków to zestaw wstępnie zainicjowanych wątków, których rozmiar może być stały lub zmienny.

Jeśli zadań jest więcej niż wątków, zadania czekają w kolejce (kolejka zadań). Z kolejki zadanie zostaje wykonane przez N-ty wątek z puli, a po wykonaniu zadania wątek pobiera nowe zadanie z kolejki. Po wykonaniu wszystkich zadań z kolejki wątki pozostają aktywne i czekają na nowe zadania. Kiedy pojawiają się zadania, wątki również zaczynają je wykonywać.

ThreadPoolExecutor

Począwszy od Javy 5, framework Executor pojawia się w rozwiązaniu wielowątkowym . Ogólnie rzecz biorąc, ma wiele komponentów i przyszedł do nas, aby rozwiązać problem wydajnego zarządzania kolejkami i pulą wątków.

Główne interfejsy to Executor i ExecutorService .

Executor to interfejs z pojedynczą metodą voidexec(Runnable runnable).

Przekazując zadanie do implementacji tej metody, wiedz, że w przyszłości będzie ono wykonywane asynchronicznie.

ExecutorService to interfejs, który dziedziczy z interfejsu Executor i udostępnia narzędzia do wykonywania zadań. Posiada również metody przerywania uruchomionego zadania i kończenia puli wątków.

ThreadPoolExecutor implementuje interfejsy Executor i ExecutorService i oddziela tworzenie zadań od wykonywania zadań. Musimy zaimplementować obiekty Runnable i wysłać je do executora, a ThreadPoolExecutor odpowiada za ich wykonanie, tworzenie instancji i pracę z wątkami.

Po przesłaniu zadania do wykonania wykorzystywany jest utworzony już wątek z puli. Rozwiązuje to problem marnowania zasobów na tworzenie i inicjowanie nowego wątku, a po użyciu - na czyszczenie go przez GC - i poprawia wydajność.

ThreadPoolExecutor ma 4 konstruktorów:


ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime, 
TimeUnit unit, 
BlockingQueue<Runnable> workQueue)
    

ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler)
    

ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime, 
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory)
    

ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime, 
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory, 
RejectedExecutionHandler handler)
    

Parametry ThreadPoolExecutor są przekazywane do konstruktora :

rozmiar puli podstawowej Parametr, który pokazuje, ile wątków będzie gotowych (uruchomionych) po uruchomieniu usługi executora.
maksymalny rozmiar puli Maksymalna liczba wątków, które może utworzyć usługa wykonawcy.
KeepAliveTime Czas, w którym uwolniony wątek będzie żył, a następnie zostanie zniszczony, jeśli liczba wątków jest większarozmiar puli podstawowej. Jednostki czasu określane są w kolejnym parametrze.
jednostka Jednostki czasu (godziny, minuty, sekundy, milisekundy itd.).
Kolejka pracy Implementacja kolejki do zadań.
treser Obsługa zadań, których nie można ukończyć.
Fabryka nici Obiekt, który tworzy nowe wątki na żądanie. Korzystanie z fabryk wątków usuwa sprzętowe wiązanie wywołań z nowym wątkiem, umożliwiając aplikacjom używanie specjalnych podklas wątków, priorytetów i tak dalej.

Tworzenie ThreadPoolExecutora

Tworzenie ThreadPoolExecutor może uprościć dla nas narzędzie klasy Executors . Ta klasa narzędziowa posiada metody, które pomogą nam przygotować obiektThreadPoolExecutor.

newFixedThreadPool — tworzy pulę wątków, która ponownie wykorzystuje ustaloną liczbę wątków do uruchamiania dowolnej liczby zadań.

ExecutorService executor = Executors.newFixedThreadPool(10);
                    
newWorkStealingPool — tworzy pulę wątków, gdzie liczba wątków = liczba rdzeni procesora dostępnych dla JVM. Domyślny poziom równoległości to jeden. Oznacza to, że w puli zostanie utworzonych tyle wątków, ile rdzeni procesora jest dostępnych dla JVM. Jeśli współbieżność wynosi 4, przekazana wartość jest używana zamiast liczby rdzeni.

ExecutorService executor = Executors.newWorkStealingPool(4);
                    
newSingleThreadExecutor — tworzy pulę z pojedynczym wątkiem do wykonywania wszystkich zadań.

ExecutorService executor = Executors.newSingleThreadExecutor();
                    
newCachedThreadPool — tworzy pulę wątków, która w razie potrzeby tworzy nowe wątki, ale ponownie wykorzystuje wcześniej utworzone wątki, gdy są dostępne.

ExecutorService executor = Executors.newCachedThreadPool();
                    
newScheduledThreadPool — tworzy pulę wątków, które mogą planować wykonywanie poleceń po określonym opóźnieniu lub wykonywanie okresowe.

ScheduledExecutorService executor = Executors.newScheduledThreadPool(10);
                    

W kolejnych wykładach rozważymy każdy z rodzajów basenów.