Runnable против Вызываемый в Java
1. обзор
С первых дней Java многопоточность была основным аспектом языка. Runnable - это основной интерфейс, предназначенный для представления многопоточных задач, аCallable - это улучшенная версияRunnable, которая была добавлена в Java 1.5.
В этой статье мы рассмотрим различия и возможности применения обоих интерфейсов.
2. Механизм исполнения
Оба интерфейса предназначены для представления задачи, которая может быть выполнена несколькими потоками. ЗадачиRunnable можно запускать с использованием классаThread илиExecutorService, тогда какCallables можно запускать только с использованием последнего.
3. Возвращаемые значения
Давайте подробнее рассмотрим, как эти интерфейсы обрабатывают возвращаемые значения.
3.1. СRunnable
Интерфейс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. СCallable
ИнтерфейсCallable - это общий интерфейс, содержащий единственный метод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. СRunnable
Поскольку в сигнатуре метода не указано предложение «throws», __ нет способа распространять дополнительные проверенные исключения.
4.2. СCallable
МетодCallable’s call() содержит предложение throwsException”, поэтому мы можем легко распространять проверенные исключения дальше:
public class FactorialTask implements Callable {
// ...
public Integer call() throws InvalidParamaterException {
if(number < 0) {
throw new InvalidParamaterException("Number should be positive");
}
// ...
}
}
В случае запускаCallable using и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.
Как всегда, доступен полный код этой статьиover on GitHub.