Do czego służy interfejs Executora?
Przed Javą 5 trzeba było samodzielnie napisać cały kod w aplikacji, aby zarządzać wątkami. Ponadto tworzenie obiektunowy wątekjest operacją zasobochłonną i tworzenie za każdym razem nowego wątku dla „lekkich” zadań nie było racjonalne. A ponieważ absolutnie wszyscy twórcy aplikacji wielowątkowych napotkali ten problem, postanowili wykupić tę funkcjonalność jako framework Executor w Javie.
Jaka jest główna idea? To proste: zamiast tworzyć nowy wątek dla każdego nowego zadania, wątki są przechowywane w pewnego rodzaju „magazynie”, a kiedy pojawia się nowe zadanie, istniejący wątek jest brany, a nowy nie jest tworzony.
Głównymi interfejsami tego frameworka są Executor , ExecutorService i ScheduledExecutorService , z których każdy rozszerza funkcjonalność poprzedniego.
Interfejs Executor jest podstawowym interfejsem, który deklaruje pojedynczą metodę void execute(Runnable command) , która uruchamia zadanie opisane w obiekcie typu Runnable .
Interfejs ExecutorService jest już bardziej interesujący. Zawiera metody zarządzania zakończeniem pracy, a także metody, które mogą zwrócić jakiś wynik. Przyjrzyjmy się bliżej jego metodom:
metoda | Opis |
---|---|
anuluj zamknięcie (); | Wywołanie metody powoduje zatrzymanie usługi ExecutorService . Wszystkie zadania, które zostały już przekazane do realizacji zostaną zrealizowane, nowe zadania nie będą przyjmowane. |
List<Runnable> shutdownNow(); |
Wywołanie metody powoduje zatrzymanie usługi ExecutorService . Wszystkie zadania, które zostały już przekazane do przetworzenia, otrzymają polecenie Thread.interrupt . Zadania w kolejce są zwracane jako lista w wyniku wywołania metody. Metoda nie czeka na zakończenie wszystkich zadań, które są „w toku” w momencie wywołania metody. Ostrzeżenie: wywołanie metody może spowodować wyciek zasobów. |
wartość logiczna isShutdown(); | Sprawdza, czy usługa ExecutorService jest zatrzymana . |
wartość logiczna isTerminated(); | Zwraca wartość true, jeśli wszystkie zadania zostały zakończone od czasu zatrzymania usługi ExecutorService . Do czasu wywołania metody shutdown() lub shutdownNow() zawsze będzie zwracana wartość false . |
boolean awaitTermination(długi limit czasu, jednostka czasu) zgłasza wyjątek InterruptedException; |
Blokuje wątek, z którego jest uruchamiany po wywołaniu metody shutdown() , dopóki nie zostanie spełniony jeden z następujących warunków:
Zwraca true , jeśli wszystkie zadania zostały zakończone, i false , jeśli limit czasu wystąpił wcześniej. |
<T> Przyszłe<T> przesyłanie(Wywoływalne<T> zadanie); |
Dodaje zadanie wywoływalne ExecutorService i zwraca obiekt, który implementuje interfejs Future . <T> to typ wyniku przesłanego zadania. |
<T> Przyszłe<T> prześlij(Zadanie możliwe do wykonania, wynik T); |
Dodaje zadanie Runnable do usługi ExecutorService i zwraca obiekt, który implementuje interfejs Future . Parametr wyniku T określa, co zwróci wywołanie metody get() odebranego obiektuprzyszły. |
Future<?> upload(Zadanie możliwe do wykonania); |
Dodaje zadanie Runnable do usługi ExecutorService i zwraca obiekt, który implementuje interfejs Future . Jeśli wywołamy metodę get() na wynikowym obiekcie Future , otrzymamy wartość null. |
<T> List<Future<T>> invokeAll(Collection<? rozszerza zadania Callable<T>>) zgłasza wyjątek InterruptedException; |
Przekazuje usłudze ExecutorService listę zadań, które można wywołać . Zwraca listę kontraktów futures, z których ma zostać uzyskany wynik operacji. Ta lista jest zwracana, gdy wszystkie przesłane zadania zostały zakończone. Jeśli kolekcja zadań zostanie zmodyfikowana podczas działania metody , wynik metody jest niezdefiniowany. |
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> zadania, long timeout, TimeUnit unit) zgłasza wyjątek InterruptedException; |
Przekazuje usłudze ExecutorService listę zadań, które można wywołać . Zwraca listę kontraktów futures, z których ma zostać uzyskany wynik operacji. Ta lista jest zwracana, gdy wszystkie przekazane zadania zakończą wykonywanie lub po upływie czasu w parametrach metody, w zależności od tego, co nastąpi wcześniej. Jeśli przekroczenie limitu czasu nastąpi wcześniej, zaległe zadania anulują ich wykonanie. Uwaga: anulowane zadanie może nie zatrzymać swojej pracy, ale kontynuować działanie (ten efekt uboczny zobaczymy na przykładzie). Jeśli kolekcja zadań zostanie zmodyfikowana podczas działania metody , wynik metody jest niezdefiniowany. |
<T> T invokeAny(Collection<? rozszerza zadania Callable<T>>) zgłasza wyjątek InterruptedException, ExecutionException; |
Przekazuje usłudze ExecutorService listę zadań, które można wywołać . Zwraca wynik jednego z zadań, które zakończyły wykonywanie bez zgłaszania wyjątku (jeśli istnieje). Jeśli kolekcja zadań zostanie zmodyfikowana podczas działania metody , wynik metody jest niezdefiniowany. |
<T> T invokeAny(Collection<? rozszerza zadania Callable<T>>, długi limit czasu, jednostka czasu) zgłasza wyjątek InterruptedException, ExecutionException, TimeoutException; |
Przekazuje usłudze ExecutorService listę zadań, które można wywołać . Zwraca wynik jednego z zadań, które zakończyły wykonywanie bez zgłoszenia wyjątku (jeśli istnieje) przed upływem czasu określonego w parametrach metody. Jeśli kolekcja zadań zostanie zmodyfikowana podczas działania metody , wynik metody jest niezdefiniowany. |
Spójrzmy na mały przykład pracy z ExecutorService .
import java.util.List;
import java.util.concurrent.*;
public class ExecutorServiceTest {
public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
//Create an ExecutorService for 2 threads
java.util.concurrent.ExecutorService executorService = new ThreadPoolExecutor(2, 2, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10));
// Create 5 tasks
MyRunnable task1 = new MyRunnable();
MyRunnable task2 = new MyRunnable();
MyRunnable task3 = new MyRunnable();
MyRunnable task4 = new MyRunnable();
MyRunnable task5 = new MyRunnable();
final List<MyRunnable> tasks = List.of(task1, task2, task3, task4, task5);
// Pass a list that contains the 5 tasks we created
final List<Future<Void>> futures = executorService.invokeAll(tasks, 6, TimeUnit.SECONDS);
System.out.println("Futures received");
// Stop the ExecutorService
executorService.shutdown();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(executorService.isShutdown());
System.out.println(executorService.isTerminated());
}
public static class MyRunnable implements Callable<Void> {
@Override
public void call() {
// Add 2 delays. When the ExecutorService is stopped, we will see which delay is in progress when the attempt is made to stop execution of the task
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
System.out.println("sleep 1: " + e.getMessage());
}
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
System.out.println("sleep 2: " + e.getMessage());
}
System.out.println("done");
return null;
}
}
}
Wyjście programu:
zrobione
mam futures
sen 1: sen przerwany
sen 1: sen przerwany
zrobione
zrobione
prawda
prawda
Każde zadanie trwa 5 sekund. Ponieważ stworzyliśmy pulę dla dwóch wątków, pierwsze dwa wiersze na wyjściu są logiczne i zrozumiałe.
6 sekund po uruchomieniu programu zostaje wyzwolony limit czasu metody invokeAll , a wynik jest zwracany jako lista Future . Widać to po linii wyjściowej „dostał futures” .
Po wykonaniu pierwszych dwóch zadań przystąpiono do pracy dwóch kolejnych. Ale ponieważ czas ustawiony w metodzie invokeAll przekroczył limit czasu, te dwa zadania nie miały czasu na wykonanie i wysłano do nich polecenie „anuluj” . Dlatego dane wyjściowe pokazują dwa wiersze „sleep 1: uśpienie przerwane” .
A potem widać jeszcze dwa napisy „gotowe” . Jest to demonstracja efektu ubocznego, o którym pisałem przy opisie metody invokeAll .
Ostatnie, piąte zadanie nawet się nie uruchomiło, więc nie widzimy nic na ten temat w wynikach programu.
Ostatnie dwa wiersze są wynikiem wywołania metod isShutdown i isTerminated .
W tym przykładzie interesujące jest również zobaczenie statusu zadań podczas debugowania po upływie limitu czasu (punkt przerwania w wierszu „executorService.shutdown();” ):
Widzimy, że dwa zadania zostały zakończone: „Ukończono normalnie” i trzy zadania „Anulowano” .
ScheduledExecutorService
Aby zakończyć opowieść o executorach, przyjrzyjmy się usłudze ScheduledExecutorService .
Ma 4 metody:
metoda | Opis |
---|---|
public ScheduledFuture<?> harmonogram (polecenie Runnable, duże opóźnienie, jednostka czasu); | Planuje uruchomienie danego zadania Runnable raz po czasie określonym jako parametr. |
public <V> ScheduledFuture<V> harmonogram (wywoływalny<V> wywoływalny, duże opóźnienie, jednostka czasu); | Planuje uruchomienie danego zadania Callable raz po czasie określonym jako parametr. |
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit); | Planuje okresowe wykonywanie danego zadania, które zostanie wykonane po raz pierwszy po czasie initialDelay , a każde kolejne uruchomienie rozpocznie się po okresie. |
public ScheduledFuture<?> ScheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit); | Planuje okresowe wykonywanie danego zadania, które najpierw wykona się po czasie initialDelay , a każde kolejne wykonanie rozpocznie się z opóźnieniem (między zakończeniem poprzedniego wykonania a rozpoczęciem bieżącego). |
GO TO FULL VERSION