So rufen Sie eine Methode in Java asynchron auf

126

Ich habe mir in letzter Zeit Go's Goroutinen angesehen und dachte, es wäre schön, etwas Ähnliches in Java zu haben. Soweit ich gesucht habe, besteht die übliche Methode zur Parallelisierung eines Methodenaufrufs darin, Folgendes zu tun:

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

Das ist nicht sehr elegant. Gibt es einen besseren Weg, dies zu tun? Ich brauchte eine solche Lösung in einem Projekt und beschloss, meine eigene Wrapper-Klasse um einen asynchronen Methodenaufruf herum zu implementieren.

Ich habe meine Wrapper-Klasse in J-Go veröffentlicht . Aber ich weiß nicht, ob es eine gute Lösung ist. Die Verwendung ist einfach:

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

oder weniger ausführlich:

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

Intern verwende ich eine Klasse, die Runnable implementiert und einige Reflection-Arbeiten ausführt, um das richtige Methodenobjekt abzurufen und aufzurufen.

Ich möchte eine Meinung zu meiner winzigen Bibliothek und zum Thema asynchrone Methodenaufrufe wie diese in Java. Ist es sicher? Gibt es schon einen einfacheren Weg?

Felipe Hummel
quelle
1
Können Sie Ihren Code von J-Go lib noch einmal anzeigen?
Msangel
Ich arbeitete an einem Projekt, in dem ich einen Stream charweise las. Sobald ein vollständiges Wort gelesen wurde, führte ich viele Operationen an diesem Wort durch. Schließlich habe ich es in eine Sammlung aufgenommen. Sobald alle Daten aus dem Stream gelesen wurden, gebe ich die Antwort zurück. Ich habe beschlossen, den Thread jedes Mal zu starten, wenn ich eine Operation an einem Wort durchführe. Aber es verringerte die Gesamtleistung. Dann habe ich erfahren, dass Threads an sich eine teure Operation sind. Ich bin nicht sicher, ob das Starten eines Threads zum gleichzeitigen Aufrufen einer Methode zu einer Leistungssteigerung führen kann, bis eine schwere E / A-Operation ausgeführt wird.
Amit Kumar Gupta
2
Tolle Frage !! Java 8 bietet hierfür CompletableFutures. Andere Antworten basieren auf wahrscheinlich alten Versionen von Java
Programmer

Antworten:

155

Ich habe gerade herausgefunden, dass es einen saubereren Weg gibt, dies zu tun

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

(Zumindest in Java 8) können Sie einen Lambda-Ausdruck verwenden, um ihn zu verkürzen:

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

So einfach wie das Erstellen einer Funktion in JS!

AegisHexad
quelle
Ihre Antwort hat meinem Problem geholfen - stackoverflow.com/questions/27009448/… . Etwas schwierig, meine Situation anzuwenden, aber es hat sich irgendwann herausgestellt :)
Deckard
Wie rufe ich mit Parametern auf?
Yatanadam
2
@yatanadam Dies kann Ihre Frage beantworten. Platzieren Sie einfach den obigen Code in einer Methode und übergeben Sie die Variable wie gewohnt. Schauen Sie sich diesen Testcode an, den ich für Sie erstellt habe.
user3004449
1
@eNnillaMS Muss der Thread nach dem Ausführen gestoppt werden? Stoppt es automatisch oder beim Garbage Collector ?
user3004449
@ user3004449 Der Thread stoppt automatisch, Sie können ihn jedoch auch erzwingen.
Shawn
58

Java 8 führte CompletableFuture ein, das im Paket java.util.concurrent.CompletableFuture verfügbar ist und verwendet werden kann, um einen asynchronen Aufruf durchzuführen:

CompletableFuture.runAsync(() -> {
    // method call or code to be asynch.
});
Rahul Chauhan
quelle
CompletableFuturewurde in einer anderen Antwort erwähnt, aber diese benutzte die ganze supplyAsync(...)Kette. Dies ist ein einfacher Wrapper, der perfekt zur Frage passt.
ndm13
ForkJoinPool.commonPool().execute()hat etwas weniger Overhead
Mark Jeronimus
31

Vielleicht möchten Sie auch die Klasse berücksichtigen java.util.concurrent.FutureTask .

Wenn Sie Java 5 oder höher verwenden, FutureTask eine schlüsselfertige Implementierung von "Eine stornierbare asynchrone Berechnung".

Das java.util.concurrentPaket bietet (z. B. ScheduledExecutorService) noch umfassendere Verhaltensweisen bei der asynchronen AusführungsplanungFutureTask möglicherweise über alle erforderlichen Funktionen.

Ich würde sogar so weit gehen zu sagen, dass es nicht mehr ratsam ist, das erste Codemuster zu verwenden, das Sie als Beispiel angegeben haben, seit es FutureTaskverfügbar ist. (Angenommen, Sie verwenden Java 5 oder höher.)

Shadit
quelle
Die Sache ist, ich möchte nur einen Methodenaufruf ausführen. Auf diese Weise müsste ich die Implementierung der Zielklasse ändern. Ich wollte genau anrufen, ohne mir Gedanken über die Implementierung von Runnable oder Callable machen zu müssen
Felipe Hummel
Ich höre dich. Java verfügt (noch) nicht über erstklassige Funktionen, daher ist dies derzeit der Stand der Technik.
Shadit
Vielen Dank für die 'zukünftigen' Schlüsselwörter ... jetzt öffne ich die Tutorials darüber ... sehr nützlich. : D
Gumuruh
4
-1 Soweit ich das beurteilen kann, reicht FutureTask allein nicht aus, um etwas asynchron auszuführen. Sie müssen noch einen Thread oder Executor erstellen, um ihn auszuführen, wie in Carlos 'Beispiel.
MikeFHay
24

Ich mag die Idee, Reflection dafür zu verwenden, nicht.
Es ist nicht nur gefährlich, es bei einem Refactoring zu verpassen, sondern kann auch von abgelehnt werden SecurityManager.

FutureTaskist eine gute Option wie die anderen Optionen aus dem Paket java.util.concurrent.
Mein Favorit für einfache Aufgaben:

    Executors.newSingleThreadExecutor().submit(task);

etwas kürzer als das Erstellen eines Threads (Aufgabe ist Callable oder Runnable)

user85421
quelle
1
Die Sache ist, ich möchte nur einen Methodenaufruf ausführen. Auf diese Weise müsste ich die Implementierung der Zielklasse ändern. Ich wollte genau anrufen, ohne mir Gedanken über die Implementierung von Runnable oder Callable machen zu müssen
Felipe Hummel
dann würde das nicht viel helfen :( aber normalerweise würde ich es vorziehen, ein Runnable oder Callable anstelle von Reflection zu verwenden
user85421
4
Nur eine kurze Anmerkung: Es ist besser, die ExecutorServiceInstanz im Auge zu behalten , damit Sie anrufen können, shutdown()wenn Sie müssen.
Daniel Szalay
1
Wenn Sie dies tun würden, würden Sie möglicherweise nicht mit nicht geschlossenem ExecutorService enden, was dazu führen würde, dass Ihre JVM das Herunterfahren verweigert? Ich würde empfehlen, eine eigene Methode zu schreiben, um einen zeitlich begrenzten ExecutorService mit einem Thread zu erhalten.
Djangofan
14

Sie können die Java8-Syntax für CompletableFuture verwenden. Auf diese Weise können Sie zusätzliche asynchrone Berechnungen basierend auf dem Ergebnis des Aufrufs einer asynchronen Funktion durchführen.

beispielsweise:

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

Weitere Details finden Sie in diesem Artikel

Tal Avissar
quelle
10

Sie können @AsyncAnmerkungen aus jcabi-Aspekten und AspectJ verwenden:

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

Wenn Sie aufrufen save(), startet ein neuer Thread und führt seinen Body aus. Ihr Haupt-Thread wird fortgesetzt, ohne auf das Ergebnis von zu warten save().

yegor256
quelle
1
Beachten Sie, dass bei diesem Ansatz alle Aufrufe zum Speichern asynchron ausgeführt werden, was möglicherweise das ist, was wir wollen oder nicht. In Fällen, in denen nur einige Aufrufe einer bestimmten Methode asynchron ausgeführt werden sollen, können andere auf dieser Seite vorgeschlagene Ansätze verwendet werden.
Bmorin
Kann ich es in einer Webanwendung verwenden (da das Verwalten von Threads dort nicht empfohlen wird)?
Onlinenaman
8

Hierfür können Sie Future-AsyncResult verwenden.

@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);
}

Referenz: https://spring.io/guides/gs/async-method/

user3177227
quelle
10
wenn Sie Spring-Boot verwenden.
Giovanni Toraldo
6

Java bietet auch eine gute Möglichkeit, asynchrone Methoden aufzurufen. In java.util.concurrent haben wir ExecutorService , der dabei hilft, dasselbe zu tun. Initialisieren Sie Ihr Objekt so -

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

und rufen Sie dann die Funktion like-

asyncExecutor.execute(() -> {

                        TimeUnit.SECONDS.sleep(3L);}
Anand
quelle
3

Es ist wahrscheinlich keine echte Lösung, aber jetzt - in Java 8 - können Sie diesen Code mit dem Lambda-Ausdruck zumindest ein wenig besser aussehen lassen.

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

Und Sie könnten dies sogar in einer Zeile tun, wobei es immer noch ziemlich lesbar ist.

new Thread(() -> x.matches("something")).start();
kcpr
quelle
Es ist wirklich schön, dies mit Java 8 zu verwenden.
Mukti
3

Sie können AsyncFuncvon Cactoos verwenden :

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

Es wird im Hintergrund ausgeführt werden , und das Ergebnis wird in verfügbar sein get()als Future.

yegor256
quelle
2

Dies hängt nicht wirklich zusammen, aber wenn ich eine Methode asynchron aufrufen würde, z. B. match (), würde ich Folgendes verwenden:

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

    });
}

Um dann die asynchrone Methode aufzurufen, würde ich verwenden:

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

Ich habe das getestet und es funktioniert. Ich dachte nur, es könnte anderen helfen, wenn sie nur für die "asynchrone Methode" kommen.

FlameBlazer
quelle
1

Es gibt auch eine schöne Bibliothek für Async-Await, die von EA erstellt wurde: https://github.com/electronicarts/ea-async

Aus ihrer Readme:

Mit 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);
    }
}

Ohne 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);
            });
    }
}
in vitro
quelle