2011-02-10 21 views

risposta

21

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.

+0

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). –

13

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.

+1

FutureTask implementa sia Future che Runnable quindi perché non può essere inviato a ExecutorService? –

5

È 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.

38

FutureTask Questa classe fornisce un base implementation of Future, con metodi per avviare e annullare un calcolo

Future è l'interfaccia

1

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".