web-dev-qa-db-fra.com

Appel de méthode aux blocs Future.get (). Est-ce vraiment souhaitable?

Veuillez lire la question attentivement avant de marquer ceci en double.

Ci-dessous l'extrait du pseudo-code. Ma question est la suivante: le code ci-dessous n'élimine-t-il pas la notion même de traitement asynchrone parallèle?

La raison pour laquelle je pose cette question est que, dans le code ci-dessous, le thread principal soumettrait une tâche à exécuter dans un autre thread. Après avoir soumis la tâche dans la file d'attente, il bloque la méthode Future.get () pour que la tâche renvoie la valeur. Je préférerais que la tâche soit exécutée dans le thread principal plutôt que de la soumettre à un autre thread et d'attendre les résultats. Qu'est-ce que j'ai gagné en exécutant la tâche dans un nouveau thread?

Je suis conscient que vous pourriez attendre pendant un temps limité, etc., mais alors si je me soucie vraiment du résultat? Le problème s'aggrave s'il y a plusieurs tâches à exécuter. Il me semble que nous ne faisons que le travail de manière synchrone. Je connais la bibliothèque Guava qui fournit une interface d’écoute non bloquante. Mais je suis intéressé de savoir si ma compréhension est correcte pour l'API Future.get (). Si tel est le cas, pourquoi Future.get () est-il conçu pour bloquer ainsi le processus de traitement en parallèle?

Remarque - Pour mémoire, j’utilise Java 6

public static void main(String[] args){

private ExectorService executorService = ...

Future future = executorService.submit(new Callable(){
    public Object call() throws Exception {
        System.out.println("Asynchronous Callable");
        return "Callable Result";
    }
});

System.out.println("future.get() = " + future.get());
}
52

Future vous propose la méthode isDone() qui n'est pas bloquante et renvoie true si le calcul est terminé, false sinon.

Future.get() est utilisé pour récupérer le résultat du calcul.

Vous avez plusieurs options:

  • appelez isDone() et si le résultat est prêt, demandez-le en appelant get(), remarquez qu'il n'y a pas de blocage
  • bloquer indéfiniment avec get()
  • bloquer pour le délai d'expiration spécifié avec get(long timeout, TimeUnit unit)

La totalité Future API _ Il est facile d'obtenir des valeurs des threads exécutant des tâches parallèles. Cela peut être fait de manière synchrone ou asynchrone si vous préférez, comme décrit dans les puces ci-dessus.

MISE À JOUR AVEC CACHE EXEMPLE

Voici une implémentation de cache de Java Concurrency In Practice , un excellent cas d'utilisation de Future.

  • Si le calcul est déjà en cours, l'appelant intéressé par le résultat du calcul attendra la fin du calcul.
  • Si le résultat est prêt dans la mémoire cache, l'appelant le recueillera.
  • si le résultat n'est pas prêt et que le calcul n'a pas encore commencé, l'appelant commencera le calcul et encapsulera le résultat dans Future pour les autres appelants.

Tout cela est facilement réalisable avec Future API.

package net.jcip.examples;

import Java.util.concurrent.*;
/**
 * Memoizer
 * <p/>
 * Final implementation of Memoizer
 *
 * @author Brian Goetz and Tim Peierls
 */
public class Memoizer <A, V> implements Computable<A, V> {
    private final ConcurrentMap<A, Future<V>> cache
            = new ConcurrentHashMap<A, Future<V>>();
    private final Computable<A, V> c;

public Memoizer(Computable<A, V> c) {
    this.c = c;
}

public V compute(final A arg) throws InterruptedException {
    while (true) {

        Future<V> f = cache.get(arg);
        // computation not started
        if (f == null) {
            Callable<V> eval = new Callable<V>() {
                public V call() throws InterruptedException {
                    return c.compute(arg);
                }
            };

            FutureTask<V> ft = new FutureTask<V>(eval);
            f = cache.putIfAbsent(arg, ft);
            // start computation if it's not started in the meantime
            if (f == null) {
                f = ft;
                ft.run();
            }
        }

        // get result if ready, otherwise block and wait
        try {
            return f.get();
        } catch (CancellationException e) {
            cache.remove(arg, f);
        } catch (ExecutionException e) {
            throw LaunderThrowable.launderThrowable(e.getCause());
        }
    }
  }
}
43
John

Ci-dessous l'extrait du pseudo-code. Ma question est la suivante: le code ci-dessous n'élimine-t-il pas la notion même de traitement asynchrone parallèle?

Tout dépend de votre cas d'utilisation:

  1. Si vous voulez vraiment bloquer jusqu'à ce que vous obteniez le résultat, utilisez le blocage get()
  2. Si vous pouvez attendre une période donnée pour connaître l'état au lieu d'une durée de blocage infinie, utilisez get() avec time-out.
  3. Si vous pouvez continuer sans analyser le résultat immédiatement et l'inspecter ultérieurement, utilisez CompletableFuture (Java 8)

    Un avenir qui peut être explicitement complété (en définissant sa valeur et son statut), et qui peut être utilisé en tant que CompletionStage, prenant en charge des fonctions dépendantes et des actions qui se déclenchent après son achèvement.

  4. Vous pouvez implémenter un mécanisme de rappel depuis votre Runnable/Callable. Regardez ci-dessous la question SE:

    Exécuteurs Java: comment être averti, sans bloquer, quand une tâche est terminée?

8
Ravindra babu

Je voudrais donner ma part sur celui-ci, plus sur le point de vue théorique car il y a déjà des réponses techniques. Je voudrais fonder ma réponse sur le commentaire:

Laissez-moi vous donner mon exemple. Les tâches que je soumets au service finissent par générer des requêtes HTTP. Le résultat de la requête HTTP peut prendre beaucoup de temps. Mais j'ai besoin du résultat de chaque requête HTTP. Les tâches sont soumises en boucle. Si j'attends que chaque tâche revienne, alors je perds le parallélisme ici, n'est-ce pas?

qui est en accord avec ce qui est dit dans la question.

Supposons que vous avez trois enfants et que vous voulez faire un gâteau pour votre anniversaire. Puisque vous voulez faire le meilleur des gâteaux, vous avez besoin de beaucoup de choses différentes pour le préparer. Donc, vous divisez les ingrédients en trois listes différentes, car là où vous habitez, il n’existe que 3 supermarchés vendant des produits différents, et attribuez à chacun de vos enfants une tâche unique, simultaneously.

Maintenant, avant que vous puissiez commencer à préparer le gâteau (supposons à nouveau que vous ayez besoin de tous les ingrédients à l’avance), vous devrez attendre que l’enfant soit obligé de faire le chemin le plus long. Maintenant, le fait que vous deviez attendre tous les ingrédients avant de commencer à faire le gâteau est votre nécessité, pas une dépendance entre les tâches. Vos enfants travaillent simultanément sur les tâches aussi longtemps qu'ils le peuvent (par exemple: jusqu'à ce que le premier enfant ait terminé la tâche). Donc, pour conclure, voici le paralelilsm.

L'exemple séquentiel est décrit lorsque vous avez 1 enfant et que vous lui attribuez les trois tâches.

1
NiVeR

Si vous ne vous souciez pas des résultats, créez un nouveau thread et utilisez-y ExectorService API pour la soumission de tâches. De cette manière, votre thread parent, c.-à-d. main thread, ne bloquera en aucune manière, il créera simplement un nouveau thread, puis démarrera une exécution ultérieure, tandis que le nouveau thread soumettra vos tâches.

Pour créer un nouveau fil - faites-le vous-même en ayant un ThreadFactory pour la création de votre fil asynchrone ou utilisez une implémentation de Java.util.concurrent.Executor.

S'il s'agit d'une application JEE et que vous utilisez la structure Spring, vous pouvez facilement créer un nouveau thread asynchrone à l'aide de @async annotation.

J'espère que cela t'aides!

1
hagrawal

Dans l’exemple que vous avez donné, vous pouvez également tout exécuter dans votre méthode main() et suivre votre chemin joyeux.

Mais supposons que vous ayez trois étapes de calcul que vous exécutez actuellement de manière séquentielle. Juste pour comprendre, supposons que step1 prenne t1 secondes, step2 prend t2 secondes et step3 prend t3 secondes. Le temps de calcul total est donc de t1+t2+t3. Aussi, supposons que t2>t1>=t3.

Considérons maintenant un scénario où nous avons exécuté ces trois étapes en parallèle en utilisant Future pour conserver chaque résultat de calcul. Vous pouvez vérifier si chaque tâche est effectuée à l'aide d'un appel isDone() non bloquant sur les futurs correspondants. Maintenant qu'est-ce qui se passe? théoriquement, votre exécution est aussi rapide que la façon dont t2 complète, non? Nous avons donc tiré des avantages du parallélisme.

De plus, en Java8, il existe CompletableFuture qui prend en charge les rappels de style fonctionnel.

1
ring bearer