Tout d'abord, je dois dire que je suis assez nouveau dans l'API Java.util.concurrent, alors peut-être que ce que je fais est complètement faux.
Que veux-je faire?
J'ai une Java qui exécute essentiellement 2 traitements distincts (appelés myFirstProcess, mySecondProcess), mais ces traitements doivent être exécutés en même temps temps.
J'ai donc essayé de faire ça:
public void startMyApplication() {
ExecutorService executor = Executors.newFixedThreadPool(2);
FutureTask<Object> futureOne = new FutureTask<Object>(myFirstProcess);
FutureTask<Object> futureTwo = new FutureTask<Object>(mySecondProcess);
executor.execute(futureOne);
executor.execute(futureTwo);
while (!(futureOne.isDone() && futureTwo.isDone())) {
try {
// I wait until both processes are finished.
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
logger.info("Processing finished");
executor.shutdown();
// Do some processing on results
...
}
myFirstProcess et mySecondProcess sont des classes qui implémentent Callable<Object>
, et où tout leur traitement est effectué dans la méthode call ().
Cela fonctionne assez bien, mais je ne suis pas sûr que ce soit la bonne façon de procéder. Est-ce un bon moyen de faire ce que je veux? Sinon, pouvez-vous me donner quelques conseils pour améliorer mon code (et le garder aussi simple que possible).
Vous feriez mieux d'utiliser la méthode get()
.
futureOne.get();
futureTwo.get();
Tous deux attendent la notification du thread indiquant qu'il a terminé le traitement, cela vous évite le temps d'attente occupé avec minuterie que vous utilisez maintenant, ce qui n'est ni efficace ni élégant.
En prime, vous disposez de l'API get(long timeout, TimeUnit unit)
qui vous permet de définir une durée maximale pour que le thread se mette en attente et attend une réponse, et sinon continue de s'exécuter.
Voir Java API pour plus d'informations.
Les utilisations de FutureTask
ci-dessus sont tolérables, mais certainement pas idiomatiques. Vous emballez en fait un extra FutureTask
autour de celui que vous avez soumis au ExecutorService
. Votre FutureTask
est traité comme un Runnable
par le ExecutorService
. En interne, il enveloppe votre FutureTask
- comme -Runnable
dans un nouveau FutureTask
et vous le renvoie sous la forme d'un Future<?>
.
Au lieu de cela, vous devez soumettre vos instances Callable<Object>
À un CompletionService
. Vous déposez deux Callable
s via submit(Callable<V>)
, puis vous retournez et appelez CompletionService#take()
deux fois (une fois pour chaque Callable
) soumis. Ces appels seront bloqués jusqu'à ce que l'une et les autres tâches soumises soient terminées.
Étant donné que vous avez déjà un Executor
en main, construisez un nouveau ExecutorCompletionService
autour de lui et déposez vos tâches dedans. Ne pas tourner et dormir en attendant; CompletionService#take()
se bloquera jusqu'à ce que l'une de vos tâches soit terminée (soit en cours d'exécution, soit annulée) ou que le thread en attente sur take()
soit interrompu.
La solution de Yuval est très bien. Comme alternative, vous pouvez également faire ceci:
ExecutorService executor = Executors.newFixedThreadPool();
FutureTask<Object> futureOne = new FutureTask<Object>(myFirstProcess);
FutureTask<Object> futureTwo = new FutureTask<Object>(mySecondProcess);
executor.execute(futureOne);
executor.execute(futureTwo);
executor.shutdown();
try {
executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
} catch (InterruptedException e) {
// interrupted
}
Quel est l'avantage de cette approche? Il n'y a pas vraiment de différence, sauf que de cette façon, vous empêchez l'exécuteur d'accepter d'autres tâches (vous pouvez aussi le faire dans l'autre sens). J'ai cependant tendance à préférer cet idiome à celui-là.
En outre, si get () lève une exception, vous pouvez vous retrouver dans une partie de votre code qui suppose que les deux tâches sont terminées, ce qui peut être mauvais.
Vous pouvez utiliser la méthode invoke all (Collection ....)
package concurrent.threadPool;
import Java.util.Arrays;
import Java.util.List;
import Java.util.concurrent.Callable;
import Java.util.concurrent.ExecutionException;
import Java.util.concurrent.ExecutorService;
import Java.util.concurrent.Executors;
import Java.util.concurrent.Future;
public class InvokeAll {
public static void main(String[] args) throws Exception {
ExecutorService service = Executors.newFixedThreadPool(5);
List<Future<Java.lang.String>> futureList = service.invokeAll(Arrays.asList(new Task1<String>(),new Task2<String>()));
System.out.println(futureList.get(1).get());
System.out.println(futureList.get(0).get());
}
private static class Task1<String> implements Callable<String>{
@Override
public String call() throws Exception {
Thread.sleep(1000 * 10);
return (String) "1000 * 5";
}
}
private static class Task2<String> implements Callable<String>{
@Override
public String call() throws Exception {
Thread.sleep(1000 * 2);
int i=3;
if(i==3)
throw new RuntimeException("Its Wrong");
return (String) "1000 * 2";
}
}
}
Vous pouvez utiliser un CyclicBarrier si vous êtes intéressé à démarrer les threads en même temps, ou à attendre qu'ils se terminent, puis à poursuivre le traitement. Voir le javadoc pour plus d'informations.
Si vos futures tâches sont supérieures à 2, veuillez considérer [ListenableFuture][1]
.
Lorsque plusieurs opérations doivent commencer dès qu'une autre opération démarre - "fan-out" - ListenableFuture fonctionne: il déclenche tous les rappels demandés. Avec un peu plus de travail, nous pouvons "fan-in", ou déclencher un ListenableFuture pour être calculé dès que plusieurs autres futurs seront tous terminés.