Asynchrone Programmierung in Java

1. Übersicht

Angesichts der wachsenden Nachfrage nach nicht blockierendem Code benötigen wir Möglichkeiten, den Code asynchron auszuführen.

In diesem Tutorial werden einige Möglichkeiten vorgestellt, wie Sie eine asynchrone Programmierung in Java erreichen können. Außerdem werden wir einige Java-Bibliotheken untersuchen, die sofort einsatzbereite Lösungen bieten.

2. Asynchrone Programmierung in Java

2.1. Faden

Wir können einen neuen Thread erstellen, um jede Operation asynchron auszuführen. Mit der Veröffentlichung von Lambda-Ausdrücken in Java 8 ist es sauberer und lesbarer.

Erstellen wir einen neuen Thread, der die Fakultät einer Zahl berechnet und druckt:

int number = 20; Thread newThread = new Thread(() -> { System.out.println("Factorial of " + number + " is: " + factorial(number)); }); newThread.start();

2.2. FutureTask

Seit Java 5 bietet die Future- Schnittstelle eine Möglichkeit, asynchrone Operationen mit FutureTask auszuführen .

Wir können die Verwendung einreichen Methode des ExecutorService die Aufgabe asynchron auszuführen , und die Instanz der Rückkehr FutureTask .

Finden wir also die Fakultät einer Zahl:

ExecutorService threadpool = Executors.newCachedThreadPool(); Future futureTask = threadpool.submit(() -> factorial(number)); while (!futureTask.isDone()) { System.out.println("FutureTask is not finished yet..."); } long result = futureTask.get(); threadpool.shutdown();

Hier haben wir die isDone- Methode verwendet, die von der Future- Schnittstelle bereitgestellt wird , um zu überprüfen, ob die Aufgabe abgeschlossen ist. Sobald wir fertig sind, können wir das Ergebnis mit der get- Methode abrufen .

2.3. CompletableFuture

Java 8 führte CompletableFuture mit einer Kombination aus Future und CompletionStage ein . Es bietet verschiedene Methoden wie supplyAsync , runAsync und dannApplyAsync für die asynchrone Programmierung.

Verwenden wir also die CompletableFuture anstelle der FutureTask , um die Fakultät einer Zahl zu ermitteln:

CompletableFuture completableFuture = CompletableFuture.supplyAsync(() -> factorial(number)); while (!completableFuture.isDone()) { System.out.println("CompletableFuture is not finished yet..."); } long result = completableFuture.get();

Wir müssen den ExecutorService nicht explizit verwenden. Die CompletableFuture verwendet ForkJoinPool intern , um die Aufgabe asynchron zu behandeln . Daher wird unser Code dadurch viel sauberer.

3. Guave

Guava bietet die ListenableFuture- Klasse zum Ausführen asynchroner Operationen.

Zuerst fügen wir die neueste Guaven- Maven-Abhängigkeit hinzu:

 com.google.guava guava 28.2-jre 

Lassen Sie uns dann die Fakultät einer Zahl mithilfe von ListenableFuture ermitteln :

ExecutorService threadpool = Executors.newCachedThreadPool(); ListeningExecutorService service = MoreExecutors.listeningDecorator(threadpool); ListenableFuture guavaFuture = (ListenableFuture) service.submit(()-> factorial(number)); long result = guavaFuture.get();

Hier stellt die MoreExecutors- Klasse die Instanz der ListeningExecutorService- Klasse bereit . Anschließend führt die ListeningExecutorService.submit- Methode die Aufgabe asynchron aus und gibt die Instanz von ListenableFuture zurück .

Guava verfügt auch über eine Futures- Klasse, die Methoden wie submitAsync , ScheduleAsync und transformAsync bereitstellt , um die ListenableFutures ähnlich wie CompletableFuture zu verketten.

Schauen wir uns zum Beispiel an, wie Futures.submitAsync anstelle der ListeningExecutorService.submit- Methode verwendet wird:

ListeningExecutorService service = MoreExecutors.listeningDecorator(threadpool); AsyncCallable asyncCallable = Callables.asAsyncCallable(new Callable() { public Long call() { return factorial(number); } }, service); ListenableFuture guavaFuture = Futures.submitAsync(asyncCallable, service);

Hier erfordert die submitAsync- Methode ein Argument von AsyncCallable , das mit der Callables- Klasse erstellt wird.

Darüber hinaus bietet die Futures- Klasse die addCallback- Methode zum Registrieren der erfolgreichen und fehlgeschlagenen Rückrufe:

Futures.addCallback( factorialFuture, new FutureCallback() { public void onSuccess(Long factorial) { System.out.println(factorial); } public void onFailure(Throwable thrown) { thrown.getCause(); } }, service);

4. EA Async

Electronic Arts brachte die asynchrone Wartefunktion von .NET über die ea-async- Bibliothek in das Java-Ökosystem .

Die Bibliothek ermöglicht das sequentielle Schreiben von asynchronem (nicht blockierendem) Code. Daher erleichtert es die asynchrone Programmierung und skaliert auf natürliche Weise.

Zuerst fügen wir der pom.xml die neueste ea-async Maven-Abhängigkeit hinzu :

 com.ea.async ea-async 1.2.3 

Lassen Sie uns dann den zuvor diskutierten CompletableFuture- Code mithilfe der von der Async- Klasse von EA bereitgestellten Methode await transformieren :

static { Async.init(); } public long factorialUsingEAAsync(int number) { CompletableFuture completableFuture = CompletableFuture.supplyAsync(() -> factorial(number)); long result = Async.await(completableFuture); }

Hier rufen wir die Async.init- Methode im statischen Block auf, um die Async- Laufzeitinstrumentierung zu initialisieren .

Die asynchrone Instrumentierung transformiert den Code zur Laufzeit und schreibt den Aufruf in die Methode await um , um sich ähnlich wie bei der Verwendung der Kette von CompletableFuture zu verhalten .

Daher ähnelt der Aufruf der Methode await dem Aufruf von Future.join.

Wir können den Parameter - javaagent JVM für die Instrumentierung zur Kompilierungszeit verwenden. Dies ist eine Alternative zur Async.init- Methode:

java -javaagent:ea-async-1.2.3.jar -cp  

Lassen Sie uns ein weiteres Beispiel für das sequentielle Schreiben von asynchronem Code untersuchen.

Zuerst führen wir einige Kettenoperationen asynchron mit den Kompositionsmethoden wie thenComposeAsync und thenAcceptAsync der CompletableFuture- Klasse aus:

CompletableFuture completableFuture = hello() .thenComposeAsync(hello -> mergeWorld(hello)) .thenAcceptAsync(helloWorld -> print(helloWorld)) .exceptionally(throwable -> { System.out.println(throwable.getCause()); return null; }); completableFuture.get();

Then, we can transform the code using EA's Async.await():

try { String hello = await(hello()); String helloWorld = await(mergeWorld(hello)); await(CompletableFuture.runAsync(() -> print(helloWorld))); } catch (Exception e) { e.printStackTrace(); }

The implementation resembles the sequential blocking code. However, the await method doesn't block the code.

As discussed, all calls to the await method will be rewritten by the Async instrumentation to work similarly to the Future.join method.

So, once the asynchronous execution of the hello method is finished, the Future result is passed to the mergeWorld method. Then, the result is passed to the last execution using the CompletableFuture.runAsync method.

5. Cactoos

Cactoos is a Java library based on object-oriented principles.

It is an alternative to Google Guava and Apache Commons that provides common objects for performing various operations.

First, let's add the latest cactoos Maven dependency:

 org.cactoos cactoos 0.43 

The library provides an Async class for asynchronous operations.

So, we can find the factorial of a number using the instance of Cactoos's Async class:

Async asyncFunction = new Async(input -> factorial(input)); Future asyncFuture = asyncFunction.apply(number); long result = asyncFuture.get();

Here, the apply method executes the operation using the ExecutorService.submit method and returns an instance of the Future interface.

Similarly, the Async class has the exec method that provides the same feature without a return value.

Note: the Cactoos library is in the initial stages of development and may not be appropriate for production use yet.

6. Jcabi-Aspects

Jcabi-Aspects provides the @Async annotation for asynchronous programming through AspectJ AOP aspects.

First, let's add the latest jcabi-aspects Maven dependency:

 com.jcabi jcabi-aspects 0.22.6  

The jcabi-aspects library requires AspectJ runtime support. So, we'll add the aspectjrt Maven dependency:

 org.aspectj aspectjrt 1.9.5  

Next, we'll add the jcabi-maven-plugin plugin that weaves the binaries with AspectJ aspects. The plugin provides the ajc goal that does all the work for us:

 com.jcabi jcabi-maven-plugin 0.14.1    ajc      org.aspectj aspectjtools 1.9.1   org.aspectj aspectjweaver 1.9.1   

So, we're all set to use the AOP aspects for asynchronous programming:

@Async @Loggable public Future factorialUsingAspect(int number) { Future factorialFuture = CompletableFuture.completedFuture(factorial(number)); return factorialFuture; }

When we compile the code, the library will inject AOP advice in place of the @Async annotation through AspectJ weaving, for the asynchronous execution of the factorialUsingAspect method.

So, let's compile the class using the Maven command:

mvn install

The output from the jcabi-maven-plugin may look like:

 --- jcabi-maven-plugin:0.14.1:ajc (default) @ java-async --- [INFO] jcabi-aspects 0.18/55a5c13 started new daemon thread jcabi-loggable for watching of @Loggable annotated methods [INFO] Unwoven classes will be copied to /tutorials/java-async/target/unwoven [INFO] jcabi-aspects 0.18/55a5c13 started new daemon thread jcabi-cacheable for automated cleaning of expired @Cacheable values [INFO] ajc result: 10 file(s) processed, 0 pointcut(s) woven, 0 error(s), 0 warning(s)

We can verify if our class is woven correctly by checking the logs in the jcabi-ajc.log file, generated by the Maven plugin:

Join point 'method-execution(java.util.concurrent.Future com.baeldung.async.JavaAsync.factorialUsingJcabiAspect(int))' in Type 'com.baeldung.async.JavaAsync' (JavaAsync.java:158) advised by around advice from 'com.jcabi.aspects.aj.MethodAsyncRunner' (jcabi-aspects-0.22.6.jar!MethodAsyncRunner.class(from MethodAsyncRunner.java))

Then, we'll run the class as a simple Java application, and the output will look like:

17:46:58.245 [main] INFO com.jcabi.aspects.aj.NamedThreads - jcabi-aspects 0.22.6/3f0a1f7 started new daemon thread jcabi-loggable for watching of @Loggable annotated methods 17:46:58.355 [main] INFO com.jcabi.aspects.aj.NamedThreads - jcabi-aspects 0.22.6/3f0a1f7 started new daemon thread jcabi-async for Asynchronous method execution 17:46:58.358 [jcabi-async] INFO com.baeldung.async.JavaAsync - #factorialUsingJcabiAspect(20): '[email protected][Completed normally]' in 44.64µs

So, we can see a new daemon thread jcabi-async is created by the library that performed the task asynchronously.

Similarly, the logging is enabled by the @Loggable annotation provided by the library.

7. Conclusion

In this article, we've seen a few ways of asynchronous programming in Java.

To begin with, we explored Java's in-built features like FutureTask and CompletableFuture for asynchronous programming. Then, we've seen a few libraries like EA Async and Cactoos with out-of-the-box solutions.

Also, we examined the support of performing tasks asynchronously using Guava's ListenableFuture and Futures classes. Last, we explored the jcabi-AspectJ library that provides AOP features through its @Async annotation for asynchronous method calls.

Wie üblich sind alle Code-Implementierungen über GitHub verfügbar.