Подтвердить что ты не робот

Какая разница между Future и FutureTask в Java?

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

4b9b3361

Ответ 1

На самом деле вы правы. Оба подхода идентичны. Обычно вам не нужно их обертывать. Если да, вы, вероятно, дублируете код в 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 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 <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();
}

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

Ответ 2

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

Future - это интерфейс

Ответ 3

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

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

Ответ 4

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

Ответ 5

Как и Mark и другие, правильно ответил, что Future - это интерфейс для FutureTask и Executor эффективно его factory; что код приложения редко реализует 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 на стр. 108 в "Java Concurrency на практике".