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