В чем разница между Future и FutureTask в Java? - PullRequest
48 голосов
/ 10 февраля 2011

Поскольку использование ExecutorService может submit Callable задачи и возвращает Future, зачем использовать FutureTask, чтобы обернуть Callable задачу и использовать метод execute?Я чувствую, что они оба делают одно и то же.

Ответы [ 5 ]

41 голосов
/ 10 февраля 2011

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

Future - интерфейс

26 голосов
/ 10 февраля 2011

На самом деле вы правы. Два подхода идентичны. Как правило, вам не нужно оборачивать их самостоятельно. Если да, вы, вероятно, дублируете код в 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. То есть Исполнитель владеет этим экземпляром.

16 голосов
/ 10 февраля 2011

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

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

7 голосов
/ 10 февраля 2011

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

3 голосов
/ 28 июня 2017

Как Марк и другие правильно ответили, что 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 на практике».

...