Poiché l'uso ExecutorService
può submit
un compito Callable
e restituire una Future
, perché hanno bisogno di usare FutureTask
per avvolgere Callable
compito e utilizzare il metodo execute
? Sento che entrambi fanno la stessa cosa.Qual è la differenza tra Future e FutureTask in Java?
risposta
In effetti hai ragione. I due approcci sono identici. In genere non è necessario avvolgerli da soli. Se si è, si sta probabilmente duplicare il codice in 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);
}
L'unica differenza tra Futuro e RunnableFuture, è il metodo 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();
}
Un buon motivo per lasciare che l'Esecutore Costruire il FutureTask per te è assicurarsi che non ci sia modo per cui esiste più di un riferimento all'istanza FutureTask. Cioè, l'Executor possiede questa istanza.
Future
è solo l'interfaccia. Dietro la scena, l'implementazione è FutureTask
.
È possibile utilizzare manualmente FutureTask
ma si perderanno i vantaggi dell'utilizzo di Executor
(thread di pooling, limite del thread, ecc.). L'utilizzo di FutureTask
è abbastanza simile all'utilizzo del vecchio Thread
e all'utilizzo del metodo eseguito.
FutureTask implementa sia Future
È necessario utilizzare FutureTask solo se si desidera modificare il suo comportamento o accedere a Callable in un secondo momento. Per il 99% degli usi, usa solo Callable e Future.
FutureTask Questa classe fornisce un base implementation of Future
, con metodi per avviare e annullare un calcolo
Future è l'interfaccia
Come Mark, e altri, risposto correttamente che Future
è l'interfaccia per FutureTask
e Executor
efficace la sua fabbrica; ciò significa che raramente il codice dell'applicazione crea istantaneamente FutureTask
. Per completare la discussione sto fornendo un esempio che mostra una situazione in cui FutureTask
è costruito e utilizzato direttamente, fuori da ogni 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
Qui, FutureTask
viene utilizzato come uno strumento di sincronizzazione, come CountdownLatch
o barriera simile primitivo. Potrebbe essere stato reimplementato utilizzando CountdownLatch
o blocchi e condizioni; FutureTask
lo rende appena incapsulato, autoesplicativo, elegante e con meno codice.
Si noti inoltre che il metodo run() di FutureTask # deve essere chiamato esplicitamente, in uno qualsiasi dei thread; non c'è un Executor in giro a farlo per te. Nel mio codice, alla fine viene eseguito dal thread principale, ma è possibile modificare il metodo get()
per chiamare run()
sul primo thread chiamando get()
, quindi il primo thread che raggiunge get()
e può essere uno qualsiasi di T1, T2 o T3, farebbe il calcolo per tutti i thread rimanenti.
In base a questa idea, il primo risultato di richiesta del thread eseguirà il calcolo per gli altri, mentre i tentativi simultanei verrebbero bloccati: si basa Memoizer, vedere Esempio di Memoizer Cache da pagina 108 in "Concurrency Java in Practice".
FutureTask.get() non getta mai un'eccezione di annullamento, mentre Future.get() lo fa. È corretto? Vedi http://docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/FutureTask.html#get(long, java.util.concurrent.TimeUnit). –