Поскольку использование ExecutorService
может submit
выполнить задачу Callable
и вернуть Future
, зачем использовать FutureTask
для переноса задачи Callable
и использовать метод execute
? Я чувствую, что они оба делают одно и то же.
В чем разница между Future и FutureTask в Java?
Ответы (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 — это интерфейс
Future
это просто интерфейс. За кулисами реализация FutureTask
.
Вы можете использовать FutureTask
вручную, но вы потеряете преимущества использования Executor
(объединение потока, ограничение потока и т. д.). Использование FutureTask
очень похоже на использование старого Thread
и использование метода run.
Вам нужно будет использовать FutureTask только в том случае, если вы хотите изменить его поведение или получить доступ к его Callable позже. В 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 на практике".
Как уже было сказано, но если говорить не в общем, а в более технических терминах, так как 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().