В чем разница между 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().