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);
Future future = 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);
Future future = executorService.submit(task);
Integer result = future.get().intValue();
} В приведенном выше тесте создается исключение ExecutionException , когда мы передаем недопустимое число. Мы можем вызвать метод getCause() для этого объекта исключения, чтобы получить исходное проверенное исключение.
Если мы не вызовем метод get() класса Future , то об исключении, вызванном методом call () , не будет сообщено, и задача по – прежнему будет отмечена как выполненная:
@Test
public void whenException_ThenCallableDoesntThrowsItIfGetIsNotCalled(){
FactorialCallableTask task = new FactorialCallableTask(-5);
Future future = executorService.submit(task);
assertEquals(false, future.isDone());
} Приведенный выше тест пройдет успешно, даже если мы выдали исключение для отрицательных значений параметра в FactorialCallableTask.
5. Заключение
В этой статье мы рассмотрели различия между интерфейсами Runnable и Callable .
Как всегда, полный код этой статьи доступен на GitHub .