В этой статье я собираюсь поговорить о двух концепциях многопоточности: исполняемой и вызываемой.

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 и чем они отличаются. Я надеюсь, тебе это нравится.