Programmazione asincrona in Java

1. Panoramica

Con la crescente domanda di scrittura di codice non bloccante, abbiamo bisogno di modi per eseguire il codice in modo asincrono.

In questo tutorial, esamineremo alcuni modi per ottenere la programmazione asincrona in Java. Inoltre, esploreremo alcune librerie Java che forniscono soluzioni pronte all'uso.

2. Programmazione asincrona in Java

2.1. Filo

Possiamo creare un nuovo thread per eseguire qualsiasi operazione in modo asincrono. Con il rilascio delle espressioni lambda in Java 8, è più pulito e più leggibile.

Creiamo un nuovo thread che calcola e stampa il fattoriale di un numero:

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

2.2. FutureTask

A partire da Java 5, l' interfaccia Future fornisce un modo per eseguire operazioni asincrone utilizzando FutureTask .

Possiamo usare il metodo submit di ExecutorService per eseguire l'attività in modo asincrono e restituire l'istanza di FutureTask .

Quindi, troviamo il fattoriale di un numero:

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

Qui, abbiamo utilizzato il metodo isDone fornito dall'interfaccia Future per verificare se l'attività è stata completata. Una volta terminato, possiamo recuperare il risultato utilizzando il metodo get .

2.3. CompletableFuture

Java 8 ha introdotto CompletableFuture con una combinazione di Future e CompletionStage . Fornisce vari metodi come supplyAsync , runAsync e quindiApplyAsync per la programmazione asincrona.

Quindi, usiamo CompletableFuture al posto di FutureTask per trovare il fattoriale di un numero:

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

Non è necessario utilizzare esplicitamente ExecutorService . Il CompletableFuture utilizza internamente ForkJoinPool per gestire l'attività in modo asincrono . Quindi, rende il nostro codice molto più pulito.

3. Guava

Guava fornisce la classe ListenableFuture per eseguire operazioni asincrone.

Innanzitutto, aggiungeremo l'ultima dipendenza guava Maven:

 com.google.guava guava 28.2-jre 

Quindi, troviamo il fattoriale di un numero usando ListenableFuture :

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

In questo caso, la classe MoreExecutors fornisce l'istanza della classe ListeningExecutorService . Quindi, il metodo ListeningExecutorService.submit esegue l'attività in modo asincrono e restituisce l'istanza di ListenableFuture .

Guava ha anche una classe Futures che fornisce metodi come submitAsync , scheduleAsync e transformAsync per concatenare ListenableFutures simile a CompletableFuture.

Ad esempio, vediamo come utilizzare Futures.submitAsync al posto del metodo ListeningExecutorService.submit :

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

Qui, il metodo submitAsync richiede un argomento di AsyncCallable , che viene creato utilizzando la classe Callables .

Inoltre, la classe Futures fornisce il metodo addCallback per registrare i callback riusciti e non riusciti:

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 ha portato la funzionalità di attesa asincrona da .NET all'ecosistema Java tramite la libreria ea-asincrona .

La libreria consente la scrittura sequenziale di codice asincrono (non bloccante). Pertanto, semplifica la programmazione asincrona e si adatta in modo naturale.

Innanzitutto, aggiungeremo l'ultima dipendenza Maven ea-async al pom.xml :

 com.ea.async ea-async 1.2.3 

Quindi, trasformiamo il codice CompletableFuture discusso in precedenza utilizzando il metodo await fornito dalla classe Async di EA :

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

Qui, effettuiamo una chiamata al metodo Async.init nel blocco statico per inizializzare la strumentazione di runtime Async .

La strumentazione asincrona trasforma il codice in fase di runtime e riscrive la chiamata al metodo await , in modo che si comporti in modo simile all'utilizzo della catena di CompletableFuture .

Pertanto, la chiamata al metodo di attesa è simile alla chiamata di Future.join.

Possiamo usare il parametro - javaagent JVM per la strumentazione in fase di compilazione. Questa è un'alternativa al metodo Async.init :

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

Esaminiamo un altro esempio di scrittura sequenziale di codice asincrono.

Innanzitutto, eseguiremo alcune operazioni a catena in modo asincrono utilizzando i metodi di composizione come thenComposeAsync e thenAcceptAsync della classe CompletableFuture :

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.

Come al solito, tutte le implementazioni del codice sono disponibili su GitHub.