Понимание Runnable Callable Future FutureTask и его применения

Java

Как правило, существует только два способа создания потока: один — наследовать Thread, а другой — реализовать интерфейс Runnable. Однако у этих двух методов создания есть фатальный недостаток, заключающийся в отсутствии возвращаемого значения, что очень огорчает людей. Немного хлопотно использовать общие переменные или другие методы связи, чтобы получить результат, обработанный потоком.

Кроме того, обычно не рекомендуется использовать наследование Thread для создания потоков, потому что Java имеет только одно наследование и не может наследовать более одного. Но Runnable — это интерфейс, поэтому вы можете сделать так, чтобы ваш класс реализации реализовывал несколько интерфейсов одновременно. И вам придется использовать пул потоков позже.Если вы использовали Runnable для его реализации раньше, вы можете напрямую передать Runnable в пул потоков для управления!

После Java 1.5 есть Callable и Future. Эти два могут предоставить результат выполнения потока!

Далее мы кратко представим Runnable, Callable, Future и FutureTask.

Runnable

Runnable - это интерфейс, просто простой метод, реализующий метод запуска и напишите код, который вы хотите выполнить, в методе запуска, но нет интерфейса возврата задачи, и исключение не может быть выдано.

public interface Runnable {
    public abstract void run();
}

Простое приложение

    Runnable runnable =
        new Runnable() {
          @Override
          public void run() {
            System.out.println("2333");
          }
        };
    Thread thread = new Thread(runnable);
    thread.start();

Callable

Callable — это тоже интерфейс.Это простой метод вызова.Просто напишите код, который вы хотите выполнить в методе вызова, и возвращаемый результат будет результатом выполнения. Отличие от Runnable в том, что он имеет возвращаемый результат и может генерировать исключение! Обычно используется с ThreadPoolExecutor.

public interface Callable<V> {
   V call() throws Exception;
}

Простое приложение, его нельзя использовать напрямую с Thread.

   Callable<String> callable =
        new Callable<String>() {
          @Override
          public String call() throws Exception {
            return "2333";
          }
        };
    ExecutorService executor = Executors.newFixedThreadPool(1);
    Future<String> result = executor.submit(callable);
    System.out.println(result.get());

Future

Future также является интерфейсом, который может отменить конкретную задачу Runnable или Callable, определить, была ли задача отменена, запросить, завершена ли задача, и получить результат задачи. Если это Runnable, возвращаемый результат будет нулевым (далее будет проанализировано, почему задача Runnable, Future также может возвращать результат). Интерфейс имеет следующие методы. Обратите внимание, что оба метода get будут блокировать поток, который в данный момент вызывает get, и не будут пробуждать текущий поток до тех пор, пока не будет возвращен результат или не истечет время ожидания.

public interface Future<V> {
  boolean cancel(boolean mayInterruptIfRunning);  // 取消任务
  boolean isCancelled();  // 判断任务是否已取消  
  boolean isDone(); // 判断任务是否已结束
  V get() throws InterruptedException, ExecutionException;// 获得任务执行结果
  // 获得任务执行结果,支持超时
  V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}

Простое приложение

    Callable<String> callable =
        new Callable<String>() {
          @Override
          public String call() throws Exception {
            return "2333";
          }
        };
    ExecutorService executor = Executors.newFixedThreadPool(1);
    Future<String> result = executor.submit(callable);
    result.cancel(true);
    System.out.println(result.isCancelled());

FutureTask

Поскольку Future — это просто интерфейс, его нельзя использовать для непосредственного создания объектов, поэтому существует следующая задача FutureTask. FutureTask больше не интерфейс, это класс. Он реализует интерфейс RunnableFuture.public class FutureTask<V> implements RunnableFuture<V>Интерфейс RunnableFuture наследует Runnable и Future.public interface RunnableFuture<V> extends Runnable, Future<V>Следовательно, он может выполняться потоком как Runnable и может иметь операции Future. Два его конструктора следующие

FutureTask(Callable<V> callable);
FutureTask(Runnable runnable, V result);

Простое приложение

FutureTask<String> futureTask = new FutureTask<>(() -> "2333");
Thread T1 = new Thread(futureTask);
T1.start();
String result = futureTask.get();
System.out.println(result);

приложение пула потоков

В общем, когда мы используем многопоточность, мы будем использовать пулы потоков, и обычно мы используем ThreadPoolExecutor для создания пулов потоков.Моя последняя статья уже описывала ThreadPoolExecutor, а здесь я добавлю использование submit.

Future<?> submit(Runnable task); // 提交 Runnable 任务
<T> Future<T> submit(Callable<T> task); // 提交 Callable 任务
<T> Future<T> submit(Runnable task, T result); // 提交 Runnable 任务及结果引用  

То есть, если нам нужно вернуть результат выполнения задачи, мы должны вызвать метод отправки вместо выполнения. submit не таинственный, он просто инкапсулирует нашу задачу и выполняет ее

    public Future<?> submit(Runnable task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<Void> ftask = newTaskFor(task, null);  //转成 RunnableFuture,传的result是null
        execute(ftask);
        return ftask;
    }

    public <T> Future<T> submit(Runnable task, T result) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task, result);
        execute(ftask);
        return ftask;
    }

    public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }

Метод newTaskFor возвращает новую задачу FutureTask. Таким образом, три метода фактически преобразуют задачу в FutureTask, и, если задача Callable, назначают ее напрямую. Если это Runnable, преобразуйте его в Callable и снова назначьте

Когда задача вызывается

    protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
        return new FutureTask<T>(callable);
    }
    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;      
    }

Когда задача выполнима

   // 按顺序看,层层调用
    protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
        return new FutureTask<T>(runnable, value);
    }
    public FutureTask(Runnable runnable, V result) {
        this.callable = Executors.callable(runnable, result);  //转 runnable 为 callable 
        this.state = NEW; 
    }
   // 以下为Executors中的方法
    public static <T> Callable<T> callable(Runnable task, T result) {
        if (task == null)
            throw new NullPointerException();
        return new RunnableAdapter<T>(task, result);
    }
    static final class RunnableAdapter<T> implements Callable<T> {  //适配器
        final Runnable task;
        final T result;
        RunnableAdapter(Runnable task, T result) {
            this.task = task;
            this.result = result;
        }
        public T call() {   
            task.run();
            return result;
        }
    }

После прочтения исходного кода тайна раскрывается, т.к. Future должен вернуть результат, поэтому внутренняя задача должна быть Callable, если задача Runnable, то я сделаю подделку, украду день и оберну жилет Callable снаружи Runnable, а возвращаемый результат находится в построении, записанном во времени.

если звонишьFuture<?> submit(Runnable task);Отправить задачу, когда она построена, это прямоRunnableFuture<Void> ftask = newTaskFor(task, null);Подключите нуль напрямую. Поэтому при вызове Future.get вы получаете нуль. Так какая от этого польза? Он используется только для оценки того, что задача была выполнена, аналогично Thread.join.

Вот еще<T> Future<T> submit(Runnable task, T result);Вам может быть интересно, какая польза от этого, вы передаете результат, что возвращается, когда Future.get не тот же результат. Да, но это работает!

class Task implements Runnable{  //定义task,传入p
    Person p;
    Task(Person r){  // 通过构造函数传入 p
      this.p = p;
    }
    void run() {
      r.setSex("男");      // 可以修改p
    }
  }
  Person p = new Person();
  p.setName("小明");
  ExecutorService executor  = Executors.newFixedThreadPool(1);
  Future<Result> future = executor.submit(new Task(p), p);  
  Person person = future.get();
  person.getSex();

Таким образом, вы можете получить модифицированный результат!

Суммировать

Подводя итог, зная разницу между Runnable и Callable, Callable может получать результаты задачи и генерировать исключения, в то время как Runnable не имеет результатов и исключения не генерируются.

Future может легко получить результат асинхронного выполнения и выполнить некоторые манипуляции над задачей. И get будет блокироваться при ожидании результата, поэтому, когда между задачами есть зависимость, одна задача зависит от результата другой задачи, и get Future может использоваться для ожидания завершения зависимой задачи.

FutureTask — это конкретный класс реализации, обладающий характеристиками Runnable и Future.Внутренний пакет — Callable.Конечно, существует также конструктор, который принимает Runnable, но он будет тайно преобразовывать Runnable в Callable для реализации методов, которые могут возвращать результаты.


Пожалуйста, поправьте меня, если я ошибаюсь! Личный публичный аккаунт: стратегия прокачки да