przegląd
od wczesnych dni Javy wielowątkowość była głównym aspektem języka. Runnable jest podstawowym interfejsem przeznaczonym do reprezentowania zadań wielowątkowych, a Callable jest ulepszoną wersją Runnable, która została dodana w Javie 1.5.
w tym artykule zbadamy różnice i zastosowania obu interfejsów.
Mechanizm wykonania
oba interfejsy są zaprojektowane do reprezentowania zadania, które może być wykonane przez wiele wątków. Uruchamialne zadania mogą być uruchamiane przy użyciu klasy Thread lub ExecutorService, podczas gdy Callables mogą być uruchamiane tylko przy użyciu tej ostatniej.
zwracane wartości
przyjrzyjmy się bliżej sposobowi, w jaki te interfejsy obsługują zwracane wartości.
3.1. Z Runnable
interfejs Runnable jest interfejsem funkcjonalnym i posiada jedną metodę run (), która nie akceptuje żadnych parametrów i nie zwraca żadnych wartości.
jest to odpowiednie dla sytuacji, w których nie szukamy wyniku wykonania wątku, na przykład rejestrowania zdarzeń przychodzących:
public interface Runnable { public void run();}
zrozummy to na przykładzie:
public class EventLoggingTask implements Runnable{ private Logger logger = LoggerFactory.getLogger(EventLoggingTask.class); @Override public void run() { logger.info("Message"); }}
w tym przykładzie wątek po prostu odczyta wiadomość z kolejki i zapisze ją w pliku dziennika. Nie ma żadnej wartości zwracanej z zadania; zadanie można uruchomić za pomocą ExecutorService:
public void executeTask() { executorService = Executors.newSingleThreadExecutor(); Future future = executorService.submit(new EventLoggingTask()); executorService.shutdown();}
w takim przypadku przyszły obiekt nie będzie posiadał żadnej wartości.
3.2. W przypadku Callable
interfejs Callable jest interfejsem generycznym zawierającym metodę single call () – która zwraca wartość generyczną V:
public interface Callable<V> { V call() throws Exception;}
przyjrzyjmy się obliczeniu silnia liczby:
public class FactorialTask implements Callable<Integer> { int number; // standard constructors public Integer call() throws InvalidParamaterException { int fact = 1; // ... for(int count = number; count > 1; count--) { fact = fact * count; } return fact; }}
wynik metody call () jest zwracany wewnątrz przyszłego obiektu:
@Testpublic void whenTaskSubmitted_ThenFutureResultObtained(){ FactorialTask task = new FactorialTask(5); Future<Integer> future = executorService.submit(task); assertEquals(120, future.get().intValue());}
Obsługa wyjątków
zobaczmy, jak odpowiednie są do obsługi wyjątków.
4.1. W przypadku Runnable
ponieważ podpis metody nie ma podanej klauzuli” throws”, nie ma możliwości propagowania dalszych sprawdzonych WYJĄTKÓW.
4.2. Metoda callable
Callable 's call() zawiera klauzulę „throws Exception”, dzięki czemu możemy w łatwy sposób propagować dalej sprawdzone wyjątki:
public class FactorialTask implements Callable<Integer> { // ... public Integer call() throws InvalidParamaterException { if(number < 0) { throw new InvalidParamaterException("Number should be positive"); } // ... }}
w przypadku uruchomienia wywołania za pomocą ExecutorService, wyjątki są gromadzone w obiekcie Future, który można sprawdzić poprzez wywołanie do Future.metoda get (). Spowoduje to wywołanie wyjątku ExecutionException-który zawija oryginalny wyjątek:
@Test(expected = ExecutionException.class)public void whenException_ThenCallableThrowsIt() { FactorialCallableTask task = new FactorialCallableTask(-5); Future<Integer> future = executorService.submit(task); Integer result = future.get().intValue();}
w powyższym teście, ExecutionException jest wyrzucany, gdy przekazujemy nieprawidłową liczbę. Możemy wywołać metodę getCause () na tym obiekcie wyjątku, aby uzyskać oryginalny wyjątek checked.
jeśli nie wykonamy wywołania metody get() klasy Future – wtedy wyjątek wywołany przez metodę call () nie zostanie ponownie zgłoszony, a zadanie nadal będzie oznaczone jako zakończone:
@Testpublic void whenException_ThenCallableDoesntThrowsItIfGetIsNotCalled(){ FactorialCallableTask task = new FactorialCallableTask(-5); Future<Integer> future = executorService.submit(task); assertEquals(false, future.isDone());}
powyższy test przejdzie pomyślnie, nawet jeśli wyrzuciliśmy wyjątek dla ujemnych wartości parametru do FactorialCallableTask.
podsumowanie
w tym artykule zbadaliśmy różnice między interfejsami Uruchamialnymi i Wywoływalnymi.
jak zawsze, Pełny kod tego artykułu jest dostępny na Githubie.
zacznij od Spring 5 i Spring Boot 2, korzystając z kursu Learn Spring:
>> sprawdź kurs