Quelle est la différence entre Future et FutureTask en Java?

Puisque l’utilisation d’ ExecutorService peut submit une tâche Callable et renvoyer un Future , pourquoi utiliser FutureTask pour envelopper la tâche FutureTask et utiliser la méthode execute ? Je pense qu’ils font tous les deux la même chose.

En fait, vous avez raison. Les deux approches sont identiques. Vous n’avez généralement pas besoin de les envelopper vous-même. Si vous l’êtes, vous risquez de dupliquer le code dans AbstractExecutorService:

 /** * Returns a RunnableFuture for the given callable task. * * @param callable the callable task being wrapped * @return a RunnableFuture which when run will call the * underlying callable and which, as a Future, will yield * the callable's result as its result and provide for * cancellation of the underlying task. * @since 1.6 */ protected  RunnableFuture newTaskFor(Callable callable) { return new FutureTask(callable); } 

La seule différence entre Future et RunnableFuture est la méthode run ():

 /** * A {@link Future} that is {@link Runnable}. Successful execution of * the run method causes completion of the Future * and allows access to its results. * @see FutureTask * @see Executor * @since 1.6 * @author Doug Lea * @param  The result type returned by this Future's get method */ public interface RunnableFuture extends Runnable, Future { /** * Sets this Future to the result of its computation * unless it has been cancelled. */ void run(); } 

Une bonne raison de laisser l’Executor construire la FutureTask pour vous est de vous assurer qu’il n’existe pas plusieurs références possibles à l’instance FutureTask. C’est-à-dire que l’exécuteur possède cette instance.

FutureTask Cette classe fournit une base implementation of Future , avec des méthodes pour démarrer et annuler un calcul

L’avenir est l’interface

Future est juste l’interface. Derrière la scène, la mise en œuvre est FutureTask .

Vous pouvez absolument utiliser manuellement FutureTask mais vous perdrez les avantages de l’utilisation d’ Executor (regroupement de threads, limitation du thread, etc.). Utiliser FutureTask est assez similaire à l’ancien Thread et à la méthode run .

Vous ne devez utiliser FutureTask que si vous souhaitez modifier son comportement ou accéder à son appelable ultérieurement. Pour 99% des utilisations, utilisez simplement Callable et Future.

Comme Mark et d’autres, ont répondu correctement, Future est l’interface pour FutureTask et Executor efficacement son usine; ce qui signifie que le code de l’application instancie rarement FutureTask directement. Pour compléter la discussion, je fournis un exemple montrant une situation où FutureTask est construit et utilisé directement, en dehors de tout Executor :

  FutureTask task = new FutureTask(()-> { 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 

Ici, FutureTask est utilisé comme outil de synchronisation, comme CountdownLatch ou une autre primitive de barrière. Il aurait pu être ré-implémenté en utilisant CountdownLatch ou des verrous et des conditions; FutureTask rend bien encapsulé, explicite, élégant et avec moins de code.

Notez également que la méthode FutureTask # run () doit être appelée explicitement, dans n’importe quel thread; il n’y a pas d’exécuteur pour le faire pour vous. Dans mon code, il est finalement exécuté par le thread principal, mais on peut modifier la méthode get() pour appeler run() sur le premier thread appelant get() , donc le premier thread atteignant get() , et il peut être l’un des T1, T2 ou T3 feraient le calcul pour tous les threads restants.

Sur cette idée – le résultat de la première demande de thread ferait le calcul pour les autres, alors que les tentatives concurrentes seraient bloquées – est basé sur Memoizer, voir l’exemple Memoizer Cache de la page 108 dans “Java Concurrency in Practice”.