1. Обзор
С первых дней Java многопоточность была одним из основных аспектов языка. Runnable -это основной интерфейс, предназначенный для представления многопоточных задач, и Callable – это улучшенная версия Runnable , которая была добавлена в Java 1.5.
В этой статье мы рассмотрим различия и приложения обоих интерфейсов.
2. Механизм исполнения
Оба интерфейса предназначены для представления задачи, которая может выполняться несколькими потоками. Выполняемые задачи могут выполняться с использованием класса Thread или ExecutorService , тогда как Вызываемые могут выполняться только с использованием последнего.
3. Возвращаемые Значения
Давайте глубже рассмотрим, как эти интерфейсы обрабатывают возвращаемые значения.
3.1. С возможностью запуска
Интерфейс Runnable является функциональным интерфейсом и имеет один метод run () , который не принимает никаких параметров и не возвращает никаких значений.
Это подходит для ситуаций, когда мы не ищем результат выполнения потока, например, ведение журнала входящих событий:
public interface Runnable { public void run(); }
Давайте разберемся в этом на примере:
public class EventLoggingTask implements Runnable{ private Logger logger = LoggerFactory.getLogger(EventLoggingTask.class); @Override public void run() { logger.info("Message"); } }
В этом примере поток просто прочитает сообщение из очереди и зарегистрирует его в файле журнала. Из задачи не возвращается значение; задача может быть запущена с помощью ExecutorService:
public void executeTask() { executorService = Executors.newSingleThreadExecutor(); Future future = executorService.submit(new EventLoggingTask()); executorService.shutdown(); }
В этом случае объект Future не будет иметь никакого значения.
3.2. С возможностью вызова
Вызываемый интерфейс – это универсальный интерфейс, содержащий один метод call () , который возвращает общее значение V :
public interface Callable{ V call() throws Exception; }
Давайте посмотрим на вычисление факториала числа:
public class FactorialTask implements Callable{ int number; // standard constructors public Integer call() throws InvalidParamaterException { int fact = 1; // ... for(int count = number; count > 1; count--) { fact = fact * count; } return fact; } }
Результат метода call() возвращается в пределах объекта Future :
@Test public void whenTaskSubmitted_ThenFutureResultObtained(){ FactorialTask task = new FactorialTask(5); Futurefuture = executorService.submit(task); assertEquals(120, future.get().intValue()); }
4. Обработка исключений
Давайте посмотрим, насколько они подходят для обработки исключений.
4.1. С возможностью запуска
Поскольку в сигнатуре метода не указано предложение “throws” , нет никакого способа распространить дальнейшие проверенные исключения.
4.2. С возможностью вызова
Метод Callable call() содержит предложение “throws Exception” , поэтому мы можем легко распространять проверенные исключения дальше:
public class FactorialTask implements Callable{ // ... public Integer call() throws InvalidParamaterException { if(number < 0) { throw new InvalidParamaterException("Number should be positive"); } // ... } }
В случае запуска вызываемого с помощью ExecutorService исключения собираются в объекте Future , который можно проверить, выполнив вызов метода Future.get () . Это вызовет исключение ExecutionException – , которое обернет исходное исключение:
@Test(expected = ExecutionException.class) public void whenException_ThenCallableThrowsIt() { FactorialCallableTask task = new FactorialCallableTask(-5); Futurefuture = executorService.submit(task); Integer result = future.get().intValue(); }
В приведенном выше тесте создается исключение ExecutionException , когда мы передаем недопустимое число. Мы можем вызвать метод getCause() для этого объекта исключения, чтобы получить исходное проверенное исключение.
Если мы не вызовем метод get() класса Future , то об исключении, вызванном методом call () , не будет сообщено, и задача по – прежнему будет отмечена как выполненная:
@Test public void whenException_ThenCallableDoesntThrowsItIfGetIsNotCalled(){ FactorialCallableTask task = new FactorialCallableTask(-5); Futurefuture = executorService.submit(task); assertEquals(false, future.isDone()); }
Приведенный выше тест пройдет успешно, даже если мы выдали исключение для отрицательных значений параметра в FactorialCallableTask.
5. Заключение
В этой статье мы рассмотрели различия между интерфейсами Runnable и Callable .
Как всегда, полный код этой статьи доступен на GitHub .