В чем разница между Future и FutureTask в Java?

С момента использования ExecutorService Можно submit Callable задание и вернуть Futureзачем использовать FutureTask обернуть Callable Задача и использовать метод execute? Я чувствую, что они оба делают одно и то же.

6 ответов

Решение

На самом деле вы правы. Два подхода идентичны. Как правило, вам не нужно оборачивать их самостоятельно. Если да, вы, скорее всего, дублируете код в AbstractExecutorService:

/**
 * Returns a <tt>RunnableFuture</tt> for the given callable task.
 *
 * @param callable the callable task being wrapped
 * @return a <tt>RunnableFuture</tt> which when run will call the
 * underlying callable and which, as a <tt>Future</tt>, will yield
 * the callable's result as its result and provide for
 * cancellation of the underlying task.
 * @since 1.6
 */
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
    return new FutureTask<T>(callable);
}

Единственная разница между Future и RunnableFuture - это метод run():

/**
 * A {@link Future} that is {@link Runnable}. Successful execution of
 * the <tt>run</tt> method causes completion of the <tt>Future</tt>
 * and allows access to its results.
 * @see FutureTask
 * @see Executor
 * @since 1.6
 * @author Doug Lea
 * @param <V> The result type returned by this Future's <tt>get</tt> method
 */
public interface RunnableFuture<V> extends Runnable, Future<V> {
    /**
     * Sets this Future to the result of its computation
     * unless it has been cancelled.
     */
    void run();
}

Хорошая причина позволить Executor сконструировать для вас FutureTask - убедиться, что нет никакого возможного способа, когда существует более одной ссылки на экземпляр FutureTask. То есть Исполнитель владеет этим экземпляром.

FutureTask Этот класс обеспечивает base implementation of Future, с методами для запуска и отмены вычислений

Будущее это интерфейс

Future это просто интерфейс. За сценой, реализация FutureTask,

Вы можете абсолютно использовать FutureTask вручную, но вы потеряете преимущества использования Executor (объединение потоков, ограничение потоков и т. д.). С помощью FutureTask очень похоже на использование старого Thread и используя метод запуска.

Вам нужно будет использовать FutureTask только в том случае, если вы хотите изменить его поведение или получить к нему доступ позже. Для 99% использования, просто используйте Callable и Future.

Как и Марк, и другие правильно ответили, что Future это интерфейс для FutureTask а также Executor эффективно его фабрика; Это означает, что код приложения редко создается FutureTask непосредственно. В дополнение к обсуждению я привожу пример, показывающий ситуацию, когда FutureTask построен и используется напрямую, вне Executor:

    FutureTask<Integer> task = new FutureTask<Integer>(()-> {
        System.out.println("Pretend that something complicated is computed");
        Thread.sleep(1000);
        return 42;
    });

    Thread t1 = new Thread(()->{
        try {
            int r = task.get();
            System.out.println("Result is " + r);
        } catch (InterruptedException | ExecutionException e) {}
    });
    Thread t2 = new Thread(()->{
        try {
            int r = task.get();
            System.out.println("Result is " + r);
        } catch (InterruptedException | ExecutionException e) {}
    });
    Thread t3 = new Thread(()->{
        try {
            int r = task.get();
            System.out.println("Result is " + r);
        } catch (InterruptedException | ExecutionException e) {}
    });

    System.out.println("Several threads are going to wait until computations is ready");
    t1.start();
    t2.start();
    t3.start();
    task.run(); // let the main thread to compute the value

Вот, FutureTask используется как инструмент синхронизации, например CountdownLatch или похожий барьерный примитив. Это можно было бы повторно реализовать, используя CountdownLatch или замки и условия; FutureTask просто делает его красиво инкапсулированным, не требующим пояснений, элегантным и с меньшим количеством кода.

Также обратите внимание, что метод FutureTask#run() должен вызываться явно в любом из потоков; нет исполнителя, чтобы сделать это за вас. В моем коде это в конечном итоге выполняется основным потоком, но можно изменить get() метод для вызова run() в первом потоке get()поэтому первая нить доходила get(), и это может быть любой из T1, T2 или T3, будет делать вычисления для всех оставшихся потоков.

На этой идее - первый поток, запрашивающий результат, будет выполнять вычисления для других, в то время как одновременные попытки будут блокироваться - основан на Memoizer, см. Пример Memoizer Cache на стр. 108 в "Java Concurrency in Practice".

Как уже упоминалось, но, говоря не в общем, а в более технических терминах, поскольку FutureTask реализует RunnableFuture, вы можете вызвать его, используя

      FutureTask<T> result = new FutureTask<T>(new #YourClassImplementingCallable());
Thread t1= new Thread(result);
t1.start();
Object<T> obj = result.get();

Это больше похоже на более старые runnable, но также имеет возможность возвращать результат через обратный вызов.

Большая сила FutureTask над Future заключается в том, что он имеет больший контроль над потоками, чем просто отправка вызываемого объекта в Future и позволяет исполнителю обрабатывать потоки.

например, вы можете вызвать здесь t1.join().

Другие вопросы по тегам