Interfejs Callable i klasa FutureTask

Zwracanie wyników przez wątki - interfejs Callable

Wątki często wykorzystujemy do zwracania wyników jakiś operacji, które zostaną wykorzystane przez inny wątek. Oczywiście moglibyśmy taką komunikację oprogramować metodami wait(), notify(), ale jest w Javie jest dedykowany interfejs, którego używamy tak samo jak Runnable'a z tą różnicą, że Callable jest interfejsem generycznym, zdolnym do zwracania dowolnego typu wyników.

Podobnie jak Runnable, Callable jest interfejsem funkcyjnym, czyli możemy używać zamiast niego lambda-wyrażenia. Tak samo jak Runnable'a, Callable'a można przekazać do puli wątków.

Taki kod wykona się całkowicie normalnie (w outpucie zobaczysz komunikat). Pojawia się pytanie jak odebrać wartość zwróconą przez obiekt Callable. Robimy to metodą get() obiektu Future zwracanego przez metodę submit() puli wątków.

Wynik wykonania:

Warto wiedzieć, że wywołanie get(), powoduje zablokowanie wątku głównego to czasu zwrócenia wartości przez Callable.

Ciekawszym użyciem metody get() jest podanie jej maksymalnego czasu w którym wątkek zwróci wynik. Jeśli wątek nie wyrobi się we wskazanym timeout'cie, wtedy get() podniesie wyjątek TimeoutException.

Przerywanie zadań Callable wysłanych do puli wątków.

Jeśli obliczenia lub jakiekolwiek inne operacje przekazane do Callable trwają zbyt długo, często chcemy zabić taki wątek. Jednak wszystko na czym możemy operować to obiekt Future przekazany przez pulę wątków. Znajduje się w nim metoda cancel() przyjmująca argument typu boolean. Działanie metody cancel() to po prostu wywołanie interrupt() na wątku w którym wykonuje się Callable. Argument określa czy cancel() może zakłócić działający już wątek (wartość false oznacza, że jeśli Callable zaczął być wykonywany, to cancel() nie zrobi nic).

Ale jeśli linijkę 41 zmodyfikujemy do postaci poniższej postaci, Output będzie już inny:

Chciałbym zwrócić uwagę na dwie bardzo ważne rzeczy. Po pierwsze spójrz, że w metodzie call() nie ma sekcji throws . (Jest to dopuszczalne, ponieważ przesłonięcie metody może rzucać mniejszą ilością wyjątków lub wyjątkami bardzej specyficznymi od metody w klasie nadrzędnej). Jeśli w Callable pojawia się wyjątek, a throws zawiera Exception to taki wyjątek zostanie przekazany do obsługi wyżej, a w przypadku puli wątków jego obsługa polega na całkowitym wyciszeniu wyjątku.

Po drugie należy uważać na stosowanie wywołania Thread.currentThread().isInterrupted() w połączeniu z Thread.sleep() i wait() ponieważ zarówno Thread.sleep() i wait() podnoszą InterruptedException. Zwrócenie takiego wyjątku powoduje skasowanie stanu wątku jako zakłóconego.

FutureTask klasa pozwalająca tworzyć callbacki dla wątków

Często wątki powinny wykonać jakąś akcję po swoim zakończeniu. Krótko mówiąc potrzebujemy miejsca w którym umieścimy kod wykonywany po zakończeniu wątki - callback'a. Służy do tego klasa FutureTask w której znajduje się metoda call(), która jest wykonywana po zakończeniu Runnable'a lub Callable'a do konstruktora FutureTask.

Zacznę od przygotowania sobie klasy, która będzie implementować Callable< Integer >:

Oraz opakowanie jej za pomocą FutureTask'a:

Obiekty typu FutureTask przekazuję do puli wątków jak Runnable czy Callable.

Jak widać callback'i działają prawidłowo. Jest to fajny sposób na obejście konieczności ręcznej implementacji zdarzeń ( tworzenia listy subskrybentów, interfejsu nasłuchującego ) etc.

Ten artykuł jest elementem poniższych kursów: