web-dev-qa-db-fra.com

Est-ce un bon moyen d'utiliser Java.util.concurrent.FutureTask?

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

37
Romain Linsolas

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.

44
Yuval Adam

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

18
seh

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.

17
cletus

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";
        }

    }
}
7
HakunaMatata

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.

5
Louis Jacomet

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.

2
Anderson