Asynchroon programmeren in Java

1. Overzicht

Met de groeiende vraag naar het schrijven van niet-blokkerende code, hebben we manieren nodig om de code asynchroon uit te voeren.

In deze tutorial zullen we een paar manieren bekijken om asynchroon programmeren in Java te bereiken. We zullen ook enkele Java-bibliotheken verkennen die kant-en-klare oplossingen bieden.

2. Asynchroon programmeren in Java

2.1. Draad

We kunnen een nieuwe thread maken om elke bewerking asynchroon uit te voeren. Met de release van lambda-expressies in Java 8 is het schoner en beter leesbaar.

Laten we een nieuwe thread maken die de faculteit van een getal berekent en afdrukt:

int nummer = 20; Thread newThread = nieuwe Thread (() -> {System.out.println ("Factorial van" + nummer + "is:" + factorial (nummer));}); newThread.start ();

2.2. FutureTask

Sinds Java 5 is het Toekomst interface biedt een manier om asynchrone bewerkingen uit te voeren met behulp van de FutureTask.

We kunnen de indienen methode van de ExecutorService om de taak asynchroon uit te voeren en het exemplaar van het FutureTask.

Laten we dus de faculteit van een getal zoeken:

ExecutorService threadpool = Executors.newCachedThreadPool (); Future futureTask = threadpool.submit (() -> faculteit (nummer)); while (! futureTask.isDone ()) {System.out.println ("FutureTask is nog niet klaar ..."); } lang resultaat = futureTask.get (); threadpool.shutdown ();

Hier hebben we de is klaar methode geleverd door de Toekomst interface om te controleren of de taak is voltooid. Als u klaar bent, kunnen we het resultaat ophalen met de krijgen methode.

2.3. CompletableFuture

Java 8 geïntroduceerd CompletableFuture met een combinatie van een Toekomst en Voltooiingsfase. Het biedt verschillende methoden, zoals supplyAsync, runAsync, en thenApplyAsync voor asynchroon programmeren.

Dus laten we de CompletableFuture in plaats van de FutureTask om de faculteit van een getal te vinden:

CompletableFuture completableFuture = CompletableFuture.supplyAsync (() -> faculteit (nummer)); while (! completableFuture.isDone ()) {System.out.println ("CompletableFuture is nog niet klaar ..."); } lang resultaat = completableFuture.get ();

We hoeven de ExecutorService uitdrukkelijk. De CompletableFuture intern gebruikt ForkJoinPool om de taak asynchroon af te handelen. Daarom maakt het onze code een stuk schoner.

3. Guave

Guave biedt de Luisterbare toekomst class om asynchrone bewerkingen uit te voeren.

Eerst voegen we de laatste toe guave Maven-afhankelijkheid:

 com.google.guava guave 28.2-jre 

Laten we vervolgens de faculteit van een getal zoeken met behulp van de Luisterbare toekomst:

ExecutorService threadpool = Executors.newCachedThreadPool (); ListeningExecutorService service = MoreExecutors.listeningDecorator (threadpool); ListenableFuture guavaFuture = (ListenableFuture) service.submit (() -> faculteit (nummer)); lang resultaat = guavaFuture.get ();

Hier de Meer uitvoerders class biedt de instantie van de ListeningExecutorService klasse. Dan de ListeningExecutorService.submit methode voert de taak asynchroon uit en retourneert de instantie van de Luisterbare toekomst.

Guava heeft ook een Futures klasse die methoden biedt zoals submitAsync, scheduleAsync, en transformAsync om de ListenableFutures vergelijkbaar met de CompletableFuture.

Laten we bijvoorbeeld eens kijken hoe u Futures.submitAsync in plaats van de ListeningExecutorService.submit methode:

ListeningExecutorService service = MoreExecutors.listeningDecorator (threadpool); AsyncCallable asyncCallable = Callables.asAsyncCallable (new Callable () {openbare lange oproep () {terug faculteit (nummer);}}, service); ListenableFuture guavaFuture = Futures.submitAsync (asyncCallable, service);

Hier de submitAsync methode vereist een argument van AsyncCallable, die is gemaakt met behulp van de Oproepen klasse.

Bovendien is het Futures klasse biedt de addCallback methode om de geslaagde en mislukte callbacks te registreren:

Futures.addCallback (factorialFuture, nieuwe FutureCallback () {public void onSuccess (Long factorial) {System.out.println (faculteit);} public void onFailure (Throwable gegooid) {throw.getCause ();}}, service);

4. EA Async

Electronic Arts bracht de async-await-functie van .NET naar het Java-ecosysteem via de ea-async bibliotheek.

De bibliotheek maakt het mogelijk om asynchrone (niet-blokkerende) code opeenvolgend te schrijven. Daarom maakt het asynchroon programmeren eenvoudiger en schaalt het op natuurlijke wijze.

Eerst voegen we de laatste toe ea-async Maven afhankelijkheid van de pom.xml:

 com.ea.async ea-async 1.2.3 

Laten we dan het eerder besproken transformeren CompletableFuture code met behulp van de wachten methode geleverd door EA's Async klasse:

statisch {Async.init (); } openbare lange factorialUsingEAAsync (int nummer) {CompletableFuture completableFuture = CompletableFuture.supplyAsync (() -> factorial (nummer)); lang resultaat = Async.await (completableFuture); }

Hier bellen we naar het Async.init methode in de statisch block om het Async runtime-instrumentatie.

Async instrumentation transformeert de code tijdens runtime en herschrijft de aanroep naar de wachten methode, om zich op dezelfde manier te gedragen als het gebruik van de keten van CompletableFuture.

Daarom de oproep naar de wachten methode is vergelijkbaar met bellen Future.join.

We kunnen de - javaagent JVM-parameter voor instrumentatie tijdens het compileren. Dit is een alternatief voor de Async.init methode:

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

Laten we een ander voorbeeld bekijken van het opeenvolgend schrijven van asynchrone code.

Eerst zullen we een paar kettingbewerkingen asynchroon uitvoeren met behulp van de compositiemethoden zoals thenComposeAsync en thenAcceptAsync van de CompletableFuture klasse:

CompletableFuture completableFuture = hallo () .thenComposeAsync (hallo -> mergeWorld (hallo)) .thenAcceptAsync (helloWorld -> print (helloWorld)). Uitzonderlijk (throwable -> {System.out.println (throwable.getCause ()); retourneer null ;}); completableFuture.get ();

Vervolgens kunnen we de code transformeren met behulp van EA's Async.await ():

probeer {String hallo = wachten (hallo ()); String helloWorld = wachten (mergeWorld (hallo)); wachten (CompletableFuture.runAsync (() -> print (helloWorld))); } catch (uitzondering e) {e.printStackTrace (); }

De implementatie lijkt op de sequentiële blokkeringscode. echter, de wachten methode blokkeert de code niet.

Zoals besproken, alle oproepen naar de wachten methode wordt herschreven door de Async instrumentatie om op dezelfde manier te werken als de Future.join methode.

Dus, zodra de asynchrone uitvoering van de Hallo methode is voltooid, de Toekomst resultaat wordt doorgegeven aan de mergeWorld methode. Vervolgens wordt het resultaat doorgegeven aan de laatste uitvoering met behulp van de CompletableFuture.runAsync methode.

5. Cactoes

Cactoos is een Java-bibliotheek gebaseerd op objectgeoriënteerde principes.

Het is een alternatief voor Google Guava en Apache Commons dat gemeenschappelijke objecten biedt voor het uitvoeren van verschillende bewerkingen.

Laten we eerst het laatste toevoegen cactussen Maven-afhankelijkheid:

 org. cactoos cactoos 0.43 

De bibliotheek biedt een Async class voor asynchrone bewerkingen.

We kunnen dus de faculteit van een getal vinden met behulp van de instantie van Cactoos Async klasse:

Async asyncFunction = nieuwe Async (input -> faculteit (input)); Future asyncFuture = asyncFunction.apply (nummer); lang resultaat = asyncFuture.get ();

Hier, de van toepassing zijn methode voert de bewerking uit met behulp van de ExecutorService. Verzenden methode en retourneert een instantie van de Toekomst koppel.

Evenzo is het Async klasse heeft de exec methode die dezelfde functie biedt zonder een retourwaarde.

Opmerking: de Cactoos-bibliotheek bevindt zich in de beginfase van ontwikkeling en is mogelijk nog niet geschikt voor productiegebruik.

6. Jcabi-aspecten

Jcabi-Aspects biedt de @Async annotatie voor asynchroon programmeren via AspectJ AOP-aspecten.

Laten we eerst het laatste toevoegen jcabi-aspecten Maven-afhankelijkheid:

 com.jcabi jcabi-aspecten 0.22.6 

De jcabi-aspecten bibliotheek vereist AspectJ-runtime-ondersteuning. Dus we voegen de aspectjrt Maven-afhankelijkheid:

 org.aspectj aspectjrt 1.9.5 

Vervolgens voegen we de jcabi-maven-plugin plug-in die de binaries met AspectJ-aspecten weeft. De plug-in biedt de ajc doel dat al het werk voor ons doet:

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

Dus we zijn helemaal klaar om de AOP-aspecten te gebruiken voor asynchroon programmeren:

@Async @Loggable openbaar Future factorialUsingAspect (int nummer) {Future factorialFuture = CompletableFuture.completedFuture (factorial (nummer)); terugkeer faculteitFuture; }

Als we de code compileren, de bibliotheek zal AOP-advies injecteren in plaats van het @Async annotatie door middel van AspectJ-weven, voor de asynchrone uitvoering van de factorialUsingAspect methode.

Dus laten we de klasse compileren met behulp van het Maven-commando:

mvn installeren

De output van de jcabi-maven-plugin kan eruit zien als:

 --- jcabi-maven-plugin: 0.14.1: ajc (standaard) @ java-async --- [INFO] jcabi-aspecten 0.18 / 55a5c13 startte nieuwe daemon thread jcabi-loggable voor het bekijken van @Loggable geannoteerde methoden [INFO] Niet-geweven klassen worden gekopieerd naar / tutorials / java-async / target / unoven [INFO] jcabi-aspecten 0.18 / 55a5c13 gestart met nieuwe daemon-thread jcabi-cacheable voor geautomatiseerde opschoning van verlopen @Cacheable-waarden [INFO] ajc-resultaat: 10 bestand (s ) verwerkt, 0 pointcut (s) geweven, 0 fout (en), 0 waarschuwing (en)

We kunnen controleren of onze klasse correct is geweven door de logboeken in het jcabi-ajc.log bestand, gegenereerd door de Maven-plug-in:

Join point 'method-execution (java.util.concurrent.Future com.baeldung.async.JavaAsync.factorialUsingJcabiAspect (int))' in Type 'com.baeldung.async.JavaAsync' (JavaAsync.java:158) geadviseerd door rond advies van 'com.jcabi.aspects.aj.MethodAsyncRunner' (jcabi-aspecten-0.22.6.jar! MethodAsyncRunner.class (van MethodAsyncRunner.java))

Vervolgens voeren we de klasse uit als een eenvoudige Java-applicatie en ziet de uitvoer er als volgt uit:

17: 46: 58.245 [main] INFO com.jcabi.aspects.aj.NamedThreads - jcabi-aspecten 0.22.6 / 3f0a1f7 startte nieuwe daemon thread jcabi-loggable voor het bekijken van @Loggable geannoteerde methoden 17: 46: 58.355 [main] INFO com.jcabi.aspects.aj.NamedThreads - jcabi-aspecten 0.22.6 / 3f0a1f7 startte nieuwe daemon-thread jcabi-async voor uitvoering van asynchrone methode 17: 46: 58.358 [jcabi-async] INFO com.baeldung.async.JavaAsync - #factorialiAsync - #factorialiAspecting (20): '[e-mail beveiligd] [normaal ingevuld]' in 44,64 µs

We kunnen dus een nieuwe daemon-thread zien jcabi-async wordt gemaakt door de bibliotheek die de taak asynchroon heeft uitgevoerd.

Evenzo wordt de logboekregistratie ingeschakeld door de @Loggable annotatie verstrekt door de bibliotheek.

7. Conclusie

In dit artikel hebben we een paar manieren gezien om asynchroon te programmeren in Java.

Om te beginnen hebben we de ingebouwde functies van Java onderzocht, zoals FutureTask en CompletableFuture voor asynchroon programmeren. Vervolgens hebben we een paar bibliotheken gezien, zoals EA Async en Cactoos met kant-en-klare oplossingen.

Ook onderzochten we de ondersteuning van het asynchroon uitvoeren van taken met behulp van Guava's Luisterbare toekomst en Futures klassen. Als laatste hebben we de jcabi-AspectJ-bibliotheek onderzocht die AOP-functies biedt via zijn @Async annotatie voor asynchrone methodeaanroepen.

Zoals gewoonlijk zijn alle code-implementaties beschikbaar op GitHub.