В этой статье я собираюсь поговорить о двух концепциях многопоточности: исполняемой и вызываемой.
1- Что такое Runnable?
Runnable - это интерфейс, в котором классы, реализующие его, будут выполняться в потоках. Здесь вы можете увидеть интерфейс Runnable. Вся ваша логика, которая должна выполняться в потоке, будет в переопределенном методе выполнения. Вы заметите, что это недействительный метод.
@FunctionalInterface public interface Runnable { public abstract void run(); }
2- Что можно вызвать?
Все, что я написал о Runnable, действительно для интерфейса Callable, кроме одного, возвращаемого типа. Метод call вернет любой тип после завершения своего выполнения.
@FunctionalInterface public interface Callable<V> { V call() throws Exception; }
3- В чем разница между Runnable и Callable?
Как мы говорили ранее, основное различие между этими двумя интерфейсами заключается в том, что метод вызова интерфейса Callable возвращает значение. Это может быть полезно во многих случаях, когда вы хотите отслеживать результат потока. Давайте посмотрим на следующие сценарии.
В приведенном ниже примере мы реализуем интерфейс Runnable и слушаем сообщения в методе выполнения. Так как ничего возвращать не нужно, здесь удобно использовать интерфейс Runnable.
public class RunnableClass implements Runnable { private MessageListener messageListener; public RunnableClass(MessageListener messageListener) { this.messageListener = messageListener; } @Override public void run() { messageListener.subscribe(message -> { // handle message }); } }
Ниже вы можете увидеть пример Вызываемого интерфейса. Мы получаем необработанную строку в качестве параметра конструктора и метода внутреннего вызова, мы передаем ее в метод isValid StringValidator, внутренняя реализация которого сейчас не имеет значения (считайте его трудоемким валидатором строк). В этом случае нам может потребоваться узнать, успешно ли завершилась проверка, поэтому мы использовали реализованный Callable, чтобы узнать об этом.
public class CallableClass implements Callable<Boolean> { private String raw; public CallableClass(String raw) { this.raw = raw; } @Override public Boolean call() { return StringValidator.isValid(raw); } }
Есть еще одно важное отличие - способ обработки исключений. Как видно из первых двух фрагментов, метод run ничего не генерирует, в то время как метод вызова генерирует исключение. Итак, это означает, что мы можем распространять любое исключение в методе вызова, но мы должны обрабатывать все исключения внутри метода запуска.
Наконец, мы посмотрим, как они могут выполняться потоками. Для бегунов,
ExecutorService executorService = Executors.newSingleThreadExecutor(); // or any other executor Runnable r = new RunnableClass(new MessageListener()); // you can pick one of the following new Thread(r).start(); CompletableFuture.runAsync(r); executorService.execute(r);
и для звонков,
ExecutorService executorService = Executors.newSingleThreadExecutor(); Callable<Boolean> callable = new CallableClass("sometext"); Future<Boolean> result = executorService.submit(callable); System.out.println(result.get().booleanValue()); // result future will return result when ready, this will also throws InterruptedException, ExecutionException but omitted here for clean sight.
В этом руководстве я рассказал о том, что такое интерфейсы Runnable и Callable и чем они отличаются. Я надеюсь, тебе это нравится.