Comment appeler de manière asynchrone une méthode en Java


127

J'ai regardé les goroutines de Go ces derniers temps et j'ai pensé que ce serait bien d'avoir quelque chose de similaire à Java. Dans la mesure où j'ai cherché, la manière courante de paralléliser un appel de méthode est de faire quelque chose comme:

final String x = "somethingelse";
new Thread(new Runnable() {
           public void run() {
                x.matches("something");             
    }
}).start();

Ce n'est pas très élégant. Existe-t-il une meilleure façon de le faire? J'avais besoin d'une telle solution dans un projet, j'ai donc décidé d'implémenter ma propre classe wrapper autour d'un appel de méthode async.

J'ai publié ma classe wrapper dans J-Go . Mais je ne sais pas si c'est une bonne solution. L'utilisation est simple:

SampleClass obj = ...
FutureResult<Integer> res = ...
Go go = new Go(obj);
go.callLater(res, "intReturningMethod", 10);         //10 is a Integer method parameter
//... Do something else
//...
System.out.println("Result: "+res.get());           //Blocks until intReturningMethod returns

ou moins verbeux:

Go.with(obj).callLater("myRandomMethod");
//... Go away
if (Go.lastResult().isReady())                //Blocks until myRandomMethod has ended
    System.out.println("Method is finished!");

En interne, j'utilise une classe qui implémente Runnable et j'effectue un travail de réflexion pour obtenir l'objet de méthode correct et l'appeler.

Je veux avoir une opinion sur ma petite bibliothèque et sur le sujet de faire des appels de méthode async comme celui-ci en Java. Est-ce sûr? Existe-t-il déjà un moyen plus simple?


1
Pouvez-vous montrer à nouveau votre code de J-Go lib?
msangel

Je travaillais sur un projet où je lisais un flux de caractères. Une fois qu'un mot complet est lu, j'effectuais de nombreuses opérations sur ce mot. Enfin je l'ai mis dans une collection. Une fois que toutes les données du flux sont lues, je renvoie la réponse. J'ai décidé de démarrer le fil pour chaque fois que j'effectue une opération sur un mot. Mais cela a diminué la performance globale. Ensuite, j'ai appris que les threads sont en soi une opération coûteuse. Je ne sais pas si le démarrage d'un thread pour appeler une méthode simultanément peut donner un ajout de performance jusqu'à ce qu'il effectue une opération d'E / S lourde.
Amit Kumar Gupta

2
Excellente question !! Java 8 fournit CompletableFutures pour cela. D'autres réponses sont basées sur probablement d'anciennes versions de Java
TriCore

Réponses:


155

Je viens de découvrir qu'il existe une manière plus propre de faire votre

new Thread(new Runnable() {
    public void run() {
        //Do whatever
    }
}).start();

(Au moins en Java 8), vous pouvez utiliser une expression lambda pour la raccourcir en:

new Thread(() -> {
    //Do whatever
}).start();

Aussi simple que de créer une fonction dans JS!


Votre réponse a aidé mon problème - stackoverflow.com/questions/27009448/… . Un peu difficile à appliquer ma situation, mais ça a finalement fonctionné :)
Deckard

Comment appeler avec des paramètres?
yatanadam

2
@yatanadam Cela peut répondre à votre question. Placez simplement le code ci-dessus dans une méthode et passez la variable comme vous le feriez normalement. Découvrez ce code de test je fait pour vous.
user3004449

1
@eNnillaMS Le thread doit-il être arrêté après son exécution? S'arrête-t-il automatiquement ou par le garbage collector ?
user3004449

@ user3004449 Le thread s'arrête automatiquement, mais vous pouvez également le forcer.
Shawn

59

Java 8 a introduit CompletableFuture disponible dans le package java.util.concurrent.CompletableFuture, peut être utilisé pour effectuer un appel asynchrone:

CompletableFuture.runAsync(() -> {
    // method call or code to be asynch.
});

CompletableFuturea été mentionné dans une autre réponse, mais celle-ci a utilisé toute la supplyAsync(...)chaîne. Ceci est un emballage simple qui répond parfaitement à la question.
ndm13

ForkJoinPool.commonPool().execute()a un peu moins de frais généraux
Mark Jeronimus

31

Vous pouvez également envisager la classe java.util.concurrent.FutureTask.

Si vous utilisez Java 5 ou une version ultérieure, FutureTask est une implémentation clé en main de «Un calcul asynchrone annulable».

Il existe des comportements de planification d'exécution asynchrone encore plus riches disponibles dans le java.util.concurrentpackage (par exemple, ScheduledExecutorService), mais FutureTaskpeuvent avoir toutes les fonctionnalités dont vous avez besoin.

J'irais même jusqu'à dire qu'il n'est plus conseillé d'utiliser le premier modèle de code que vous avez donné comme exemple depuis qu'il FutureTaskest devenu disponible. (En supposant que vous utilisez Java 5 ou une version ultérieure.)


Le truc, c'est que je veux juste exécuter un appel de méthode. De cette façon, je devrais changer l'implémentation de la classe cible. Ce que je voulais, c'est appeler exactement sans avoir à me soucier de l'implémentation de Runnable ou Callable
Felipe Hummel

Je t'entends. Java n'a pas (encore) de fonctions de première classe, c'est donc l'état de l'art en ce moment.
shadit

merci pour les mots-clés «futurs» ... maintenant j'ouvre les tutoriels à leur sujet ... très utiles. : D
gumuruh

4
-1 pour autant que je sache, FutureTask ne suffit pas à lui seul pour exécuter quoi que ce soit de manière asynchrone. Vous devez toujours créer un Thread ou un Executor pour l'exécuter, comme dans l'exemple de Carlos.
MikeFHay

24

Je n'aime pas l'idée d'utiliser Reflection pour ça.
Non seulement dangereux de l'avoir manqué dans une refactorisation, mais il peut également être nié par SecurityManager.

FutureTaskest une bonne option comme les autres options du package java.util.concurrent.
Mon préféré pour les tâches simples:

    Executors.newSingleThreadExecutor().submit(task);

un peu plus court que la création d'un thread (la tâche est un appelable ou un exécutable)


1
Le truc, c'est que je veux juste exécuter un appel de méthode. De cette façon, je devrais changer l'implémentation de la classe cible. Ce que je voulais, c'est exactement appeler sans avoir à me soucier de l'implémentation de Runnable ou Callable
Felipe Hummel

alors cela n'aiderait pas beaucoup: (mais normalement je préférerais utiliser un Runnable ou Callable au lieu de Reflection
user85421

4
Juste une petite note: il est plus agréable de garder une trace de l' ExecutorServiceinstance, afin que vous puissiez appeler shutdown()lorsque vous en avez besoin.
Daniel Szalay

1
Si vous faisiez cela, ne vous retrouveriez-vous pas avec ExecutorService non fermé, provoquant le refus de votre JVM de s'arrêter? Je recommanderais d'écrire votre propre méthode pour obtenir un ExecutorService à un seul thread et limité dans le temps.
djangofan

14

Vous pouvez utiliser la syntaxe Java8 pour CompletableFuture, de cette façon, vous pouvez effectuer des calculs asynchrones supplémentaires basés sur le résultat de l'appel d'une fonction asynchrone.

par exemple:

 CompletableFuture.supplyAsync(this::findSomeData)
                     .thenApply(this:: intReturningMethod)
                     .thenAccept(this::notify);

Plus de détails peuvent être trouvés dans cet article


10

Vous pouvez utiliser l' @Asyncannotation de jcabi-aspects et AspectJ:

public class Foo {
  @Async
  public void save() {
    // to be executed in the background
  }
}

Lorsque vous appelez save(), un nouveau thread démarre et exécute son corps. Votre thread principal continue sans attendre le résultat de save().


1
Notez que cette approche rendrait tous les appels à enregistrer asynchrones, ce qui pourrait ou non être ce que nous voulons. Dans les cas où seuls certains appels à une méthode donnée doivent être effectués de manière asynchrone, d'autres approches suggérées sur cette page peuvent être utilisées.
bmorin

Puis-je l'utiliser dans une application Web (car la gestion des threads n'y est pas recommandée)?
onlinenaman

8

Vous pouvez utiliser Future-AsyncResult pour cela.

@Async
public Future<Page> findPage(String page) throws InterruptedException {
    System.out.println("Looking up " + page);
    Page results = restTemplate.getForObject("http://graph.facebook.com/" + page, Page.class);
    Thread.sleep(1000L);
    return new AsyncResult<Page>(results);
}

Référence: https://spring.io/guides/gs/async-method/


10
si vous utilisez spring-boot.
Giovanni Toraldo

6

Java fournit également un bon moyen d'appeler des méthodes asynchrones. dans java.util.concurrent, nous avons ExecutorService qui aide à faire de même. Initialisez votre objet comme ceci -

 private ExecutorService asyncExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

puis appelez la fonction comme-

asyncExecutor.execute(() -> {

                        TimeUnit.SECONDS.sleep(3L);}

3

Ce n'est probablement pas une vraie solution, mais maintenant - dans Java 8 - vous pouvez rendre ce code au moins un peu meilleur en utilisant l'expression lambda.

final String x = "somethingelse";
new Thread(() -> {
        x.matches("something");             
    }
).start();

Et vous pouvez même le faire en une seule ligne, en l'ayant toujours assez lisible.

new Thread(() -> x.matches("something")).start();

C'est vraiment bien d'utiliser ceci avec Java 8.
Mukti

3

Vous pouvez utiliser à AsyncFuncpartir de Cactoos :

boolean matches = new AsyncFunc(
  x -> x.matches("something")
).apply("The text").get();

Il sera exécuté en arrière-plan et le résultat sera disponible au get()format Future.


2

Ce n'est pas vraiment lié, mais si je devais appeler une méthode de manière asynchrone, par exemple matches (), j'utiliserais:

private final static ExecutorService service = Executors.newFixedThreadPool(10);
public static Future<Boolean> matches(final String x, final String y) {
    return service.submit(new Callable<Boolean>() {

        @Override
        public Boolean call() throws Exception {
            return x.matches(y);
        }

    });
}

Ensuite, pour appeler la méthode asynchrone, j'utiliserais:

String x = "somethingelse";
try {
    System.out.println("Matches: "+matches(x, "something").get());
} catch (InterruptedException e) {
    e.printStackTrace();
} catch (ExecutionException e) {
    e.printStackTrace();
}

J'ai testé cela et cela fonctionne. Je pensais juste que cela pourrait aider les autres s'ils venaient simplement pour la "méthode asynchrone".


1

Il existe également une belle bibliothèque pour Async-Await créée par EA: https://github.com/electronicarts/ea-async

De leur Readme:

Avec EA Async

import static com.ea.async.Async.await;
import static java.util.concurrent.CompletableFuture.completedFuture;

public class Store
{
    public CompletableFuture<Boolean> buyItem(String itemTypeId, int cost)
    {
        if(!await(bank.decrement(cost))) {
            return completedFuture(false);
        }
        await(inventory.giveItem(itemTypeId));
        return completedFuture(true);
    }
}

Sans EA Async

import static java.util.concurrent.CompletableFuture.completedFuture;

public class Store
{
    public CompletableFuture<Boolean> buyItem(String itemTypeId, int cost)
    {
        return bank.decrement(cost)
            .thenCompose(result -> {
                if(!result) {
                    return completedFuture(false);
                }
                return inventory.giveItem(itemTypeId).thenApply(res -> true);
            });
    }
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.