Útmutató a CompletableFuture-hoz

1. Bemutatkozás

Ez az oktatóanyag útmutatást nyújt a CompletableFuture osztály, amelyet Java 8 Concurrency API fejlesztésként mutattak be.

2. Aszinkron számítás a Java-ban

Az aszinkron számítást nehéz megalapozni. Általában minden számítást lépéssorozatként akarunk elképzelni, de aszinkron számítás esetén A visszahívásként megjelenített műveletek általában vagy szétszóródnak a kódban, vagy mélyen egymásba ágyazódnak. A helyzet még rosszabbá válik, ha kezelnünk kell azokat a hibákat, amelyek az egyik lépés során előfordulhatnak.

A Jövő felületet adtak hozzá a Java 5-höz, hogy aszinkron számítás eredményeként szolgáljon, de nem volt módja ezeknek a számításoknak az egyesítésére vagy az esetleges hibák kezelésére.

A Java 8 bemutatta a CompletableFuture osztály. Együtt a Jövő interfészen keresztül megvalósította a CompletionStage felület. Ez az interfész meghatározza az aszinkron számítási lépés szerződését, amelyet más lépésekkel kombinálhatunk.

CompletableFuture egyidejűleg építőköve és kerete, azzal körülbelül 50 különböző módszer aszinkron számítási lépések összeállításához, kombinálásához és végrehajtásához, valamint a hibák kezeléséhez.

Egy ilyen nagy API elsöprő lehet, de ezek többnyire több egyértelmű és elkülönülő használati esetben esnek.

3. Használata CompletableFuture mint Egyszerű Jövő

Először is a CompletableFuture osztály hajtja végre a Jövő interfész, így tudjuk használja a-ként Jövő megvalósítással, de további befejezési logikával.

Például létrehozhatunk ennek az osztálynak egy példányát egy arg nélküli konstruktorral, hogy megjelenítsen valamilyen jövőbeli eredményt, kiosztja a fogyasztóknak, és a jövőben bármikor kiegészítheti a teljes módszer. A fogyasztók használhatják a kap módszer az aktuális szál blokkolásához, amíg ez az eredmény meg nem jelenik.

Az alábbi példában van egy módszerünk, amely létrehozza a CompletableFuture példát, majd egy másik szálban számításokat végez és visszaadja a Jövő azonnal.

A számítás befejezése után a módszer befejezi a Jövő az eredménynek az teljes módszer:

public Future calcAsync () dobja az InterruptedException {CompletableFuture completeableFuture = new CompletableFuture (); Executors.newCachedThreadPool (). Subm (() -> {Thread.sleep (500); completeableFuture.complete ("Hello"); return null;}); return completeableFuture; }

A számítás leállításához a Végrehajtó API. Ez a módszer a létrehozására és kitöltésére a CompletableFuture bármely párhuzamossági mechanizmussal vagy API-val együtt használható, beleértve a nyers szálakat is.

Figyelje meg a kiszámítjaAsync metódus a Jövő példa.

Egyszerűen hívjuk a módszert, megkapjuk a Jövő példányt, és hívja a kap módszer, amikor készen állunk az eredmény blokkolására.

Figyelje meg azt is, hogy a kap A módszer néhány ellenőrzött kivételt dob, mégpedig ExecutionException (a számítás során bekövetkezett kivétel összefoglalása) és InterruptedException (kivétel, amely azt jelzi, hogy egy metódust végrehajtó szál megszakadt):

Future completeableFuture = calcAsync (); // ... Karakterlánc eredménye = completeableFuture.get (); assertEquals ("Hello", eredmény);

Ha már ismerjük a számítás eredményét, használhatjuk a statikus completeFuture metódus argumentummal, amely ennek a számításnak az eredményét képviseli. Következésképpen a kap módszere Jövő soha nem fogja blokkolni, azonnal visszaadja ezt az eredményt:

Future completeableFuture = CompletableFuture.completedFuture ("Hello"); // ... Karakterlánc eredménye = completeableFuture.get (); assertEquals ("Hello", eredmény);

Alternatív forgatókönyvként érdemes lehet törölje a. végrehajtását Jövő.

4. CompletableFuture tokozott számítási logikával

A fenti kód lehetővé teszi számunkra, hogy kiválasszuk az egyidejű végrehajtás bármely mechanizmusát, de mi van, ha át akarjuk hagyni ezt a kazánt, és egyszerűen aszinkron végrehajtunk néhány kódot?

Statikus módszerek runAsync és supplyAsync lehetővé tesszük számunkra a CompletableFuture példány ki Futható és Támogató funkcionális típusok ennek megfelelően.

Mindkét Futható és Támogató olyan funkcionális interfészek, amelyek lehetővé teszik példányaik továbbítását lambda kifejezésekként az új Java 8 szolgáltatásnak köszönhetően.

A Futható Az interfész ugyanaz a régi felület, amelyet a szálakban használnak, és nem teszi lehetővé az érték visszaadását.

A Támogató Az interfész egy általános metódus, egyetlen metódussal, amely nem tartalmaz argumentumokat, és egy paraméterezett típusú értéket ad vissza.

Ez lehetővé teszi számunkra, hogy adja meg a Támogató mint lambda kifejezés, amely elvégzi a számítást és visszaadja az eredményt. Olyan egyszerű, mint:

CompletableFuture future = CompletableFuture.supplyAsync (() -> "Hello"); // ... assertEquals ("Hello", future.get ());

5. Az aszinkron számítások eredményeinek feldolgozása

A számítás eredményének feldolgozásának legáltalánosabb módja az, hogy betápláljuk egy függvénybe. A akkor Alkalmazzon módszer pontosan ezt teszi; elfogadja a Funkció például felhasználja az eredmény feldolgozására, és visszaadja a Jövő amely egy függvény által visszaadott értéket tartalmaz:

CompletableFuture completeableFuture = CompletableFuture.supplyAsync (() -> "Hello"); CompletableFuture future = completeableFuture .thenApply (s -> s + "Világ"); assertEquals ("Hello World", future.get ());

Ha nem kell egy értéket visszaadnunk a Jövő lánc, használhatjuk a Fogyasztó funkcionális interfész. Egyetlen metódusa felvesz egy paramétert és visszatér üres.

Van egy módszer erre a felhasználási esetre a CompletableFuture. A akkor fogadja el módszer megkapja a Fogyasztó és átadja a számítás eredményét. Aztán a döntő future.get () A call a Üres típus:

CompletableFuture completeableFuture = CompletableFuture.supplyAsync (() -> "Hello"); CompletableFuture future = completeableFuture .thenAccept (s -> System.out.println ("A számítás visszaadva:" + s)); jövő.get ();

Végül, ha nincs szükségünk a számítás értékére, és nem akarunk visszaadni valamilyen értéket a lánc végén, akkor átadhatunk egy Futható lambda a akkorFuss módszer. A következő példában egyszerűen kinyomtatunk egy sort a konzolba, miután meghívtuk a future.get ():

CompletableFuture completeableFuture = CompletableFuture.supplyAsync (() -> "Hello"); CompletableFuture future = completeableFuture .thenRun (() -> System.out.println ("A számítás kész.")); jövő.get ();

6. A határidős ügyek egyesítése

A legjobb része a CompletableFuture Az API az kombinációs képesség CompletableFuture példányok a számítási lépések láncolatában.

Ennek a láncolásnak az eredménye maga a CompletableFuture amely lehetővé teszi a további láncolást és kombinációt. Ez a megközelítés a funkcionális nyelvekben mindenütt jelen van, és gyakran monád tervezési mintaként emlegetik.

A következő példában a akkorKomponálj módszer kettő láncolására Határidős egymás után.

Vegye figyelembe, hogy ez a módszer olyan függvényt vesz fel, amely a-t adja vissza CompletableFuture példa. Ennek a függvénynek az argumentuma az előző számítási lépés eredménye. Ez lehetővé teszi számunkra, hogy ezt az értéket a következőn belül felhasználjuk CompletableFuture’S lambda:

CompletableFuture completeableFuture = CompletableFuture.supplyAsync (() -> "Hello") .thenCompose (s -> CompletableFuture.supplyAsync (() -> s + "Világ")); assertEquals ("Hello World", completeableFuture.get ());

A akkorKomponálj módszerrel együtt akkor alkalmazzon, valósítsa meg a monádikus minta alapvető építőköveit. Szorosan kapcsolódnak a térkép és flatMap módszerei Folyam és Választható osztályok Java 8-ban is elérhetők.

Mindkét módszer megkap egy függvényt és alkalmazza azt a számítási eredményre, de a akkorKomponálj (flatMap) módszer olyan funkciót kap, amely egy másik, ugyanolyan típusú objektumot ad vissza. Ez a funkcionális struktúra lehetővé teszi ezen osztályok példányainak építését.

Ha két függetlenet akarunk végrehajtani Határidős és tegyünk valamit az eredményeikkel, használhatjuk a akkorKombinálja módszer, amely elfogadja a Jövő és a Funkció két érvvel mindkét eredmény feldolgozásához:

CompletableFuture completeableFuture = CompletableFuture.supplyAsync (() -> "Hello") .theCombine (CompletableFuture.supplyAsync (() -> "Világ"), (s1, s2) -> s1 + s2)); assertEquals ("Hello World", completeableFuture.get ());

Egyszerűbb eset, amikor kettővel akarunk valamit kezdeni Határidős’Eredményeket, de nem kell semmilyen eredő értéket levinni a Jövő lánc. A akkor fogadja el Mindkettőt módszer segít:

CompletableFuture future = CompletableFuture.supplyAsync (() -> "Hello") .theAcceptBoth (CompletableFuture.supplyAsync (() -> "Világ"), (s1, s2) -> System.out.println (s1 + s2));

7. Különbség majdAlkalmaz () és thenCompose ()

Korábbi szakaszainkban példákat mutattunk a következőkre vonatkozóan majdAlkalmaz () és thenCompose (). Mindkét API különbözõ láncot segít CompletableFuture hívások, de ennek a 2 funkciónak a használata más és más.

7.1. majdAlkalmaz ()

Ezzel a módszerrel dolgozhatunk az előző hívás eredményével. Fontos megjegyezni azonban, hogy a visszahívási típust az összes hívás összevonja.

Tehát ez a módszer akkor hasznos, ha átalakítani akarjuk az a eredményét CompletableFuture hívás:

CompletableFuture finalResult = compute (). ThenApply (s-> s + 1);

7.2. thenCompose ()

A thenCompose () módszer hasonló majdAlkalmaz () abban, hogy mindkettő új befejezési szakaszt ad vissza. Azonban, thenCompose () az előző szakaszt használja érvként. Elsimul és visszatér a Jövő közvetlenül az eredménnyel, nem pedig beágyazott jövővel, amint azt megfigyeltük majdApply ():

CompletableFuture computeAnother (Integer i) {return CompletableFuture.supplyAsync (() -> 10 + i); } CompletableFuture finalResult = compute (). ThenCompose (this :: computeAnother);

Tehát ha az ötlet a láncolás CompletableFuture módszereket akkor jobb használni thenCompose ().

Ezenkívül vegye figyelembe, hogy a két módszer közötti különbség analóg a két módszer közötti különbséggel térkép() és flatMap ().

8. Többszörös futása Határidős párhuzamosan

Amikor többször kell végrehajtanunk Határidős ezzel párhuzamosan általában meg akarjuk várni, amíg mindegyikük végrehajtja, majd feldolgozza az összesített eredményeket.

A CompletableFuture.allOf statikus módszer lehetővé teszi az összes Határidős var-arg-ként megadva:

CompletableFuture future1 = CompletableFuture.supplyAsync (() -> "Hello"); CompletableFuture future2 = CompletableFuture.supplyAsync (() -> "Gyönyörű"); CompletableFuture future3 = CompletableFuture.supplyAsync (() -> "Világ"); CompletableFuture combinedFuture = CompletableFuture.allOf (jövő1, jövő2, jövő3); // ... combinedFuture.get (); assertTrue (jövő1.isDone ()); assertTrue (future2.isDone ()); assertTrue (jövő3.isDone ());

Figyelje meg, hogy a CompletableFuture.allOf () egy CompletableFuture. Ennek a módszernek az a korlátja, hogy nem adja vissza az összesített eredményeket Határidős. Ehelyett manuálisan kell eredményeket kapnunk Határidős. Szerencsére, CompletableFuture.join () módszer és a Java 8 Streams API egyszerűvé teszi:

Karakterlánc kombinált = Stream.of (jövő1, jövő2, jövő3) .térkép (CompletableFuture :: csatlakozás) .collect (Collectors.joining ("")); assertEquals ("Hello Hello World", összesítve);

A CompletableFuture.join () módszer hasonló a kap metódus, de ellenőrizetlen kivételt dob, ha a Jövő nem fejeződik be normálisan. Ez lehetővé teszi metódus referenciaként való használatát a Stream.map () módszer.

9. A hibák kezelése

Az aszinkron számítási lépések láncában történő hibakezeléshez adaptálnunk kell a dobás / elkapás idióma hasonló módon.

Ahelyett, hogy egy szintaktikai blokkban kifogna egy kivételt, a CompletableFuture osztály lehetővé teszi számunkra, hogy speciálisan kezeljük fogantyú módszer. Ez a módszer két paramétert kap: egy számítás eredményét (ha sikeresen befejeződött), és a dobott kivételt (ha valamelyik számítási lépés nem fejeződött be normálisan).

A következő példában a fogantyú metódus az alapértelmezett érték megadásához, amikor egy üdvözlet aszinkron kiszámítása hibával zárult, mert nem adott meg nevet:

Karakterlánc neve = null; // ... CompletableFuture completeableFuture = CompletableFuture.supplyAsync (() -> {if (name == null) {dobjon új RuntimeException-t ("Számítási hiba!");} Return "Hello", + név;})}). fogantyú ((s, t) -> s! = null? s: "Helló, idegen!"); assertEquals ("Helló, idegen!", completeableFuture.get ());

Alternatív forgatókönyvként tegyük fel, hogy manuálisan szeretnénk kitölteni a Jövő értékkel, mint az első példában, de képesek kivétellel kitölteni. A kivételesen teljes módszert éppen arra szánják. A completeableFuture.get () módszer a következő példában dob egy ExecutionException val,-vel RuntimeException mint oka:

CompletableFuture completeableFuture = új CompletableFuture (); // ... completeableFuture.completeExceptionally (új RuntimeException ("A számítás sikertelen volt!")); // ... completeableFuture.get (); // ExecutionException

A fenti példában a kivételt a fogantyú módszer aszinkron módon, de a kap módszerrel a szinkron kivétel-feldolgozás tipikusabb megközelítését alkalmazhatjuk.

10. Async módszerek

A folyékony API legtöbb módszere CompletableFuture osztálynak két további változata van a Async postfix. Ezeket a módszereket általában arra szánják a végrehajtás megfelelő lépésének futtatása egy másik szálon.

A módszerek a Async A postfix futtatja a következő végrehajtási fázist egy hívó szál segítségével. Ezzel szemben a Async módszer a Végrehajtó argument futtat egy lépést a közös használatával villa / csatlakozás - a Végrehajtó hogy a ForkJoinPool.commonPool () módszer. Végül a Async módszer egy Végrehajtó argument futtat egy lépést az átadott felhasználásával Végrehajtó.

Itt van egy módosított példa, amely az a-val végzett számítás eredményét dolgozza fel Funkció példa. Az egyetlen látható különbség a majdApplyAsync módszerrel, de a motorháztető alatt egy függvény alkalmazása a ForkJoinTask példány (további információkért a villa / csatlakozás keresse meg a „Útmutató a Fork / Join Framework Java-jához” című cikket). Ez lehetővé teszi számításunk még párhuzamosabbá tételét és a rendszererőforrások hatékonyabb felhasználását:

CompletableFuture completeableFuture = CompletableFuture.supplyAsync (() -> "Hello"); CompletableFuture future = completeableFuture .thenApplyAsync (s -> s + "Világ"); assertEquals ("Hello World", future.get ());

11. JDK 9 CompletableFuture API

A Java 9 továbbfejlesztette a CompletableFuture API a következő változtatásokkal:

  • Új gyári módszerek kerültek hozzá
  • A késések és az időkorlátok támogatása
  • Javított támogatás az alosztályokhoz

és új példány API-k:

  • Executor defaultExecutor ()
  • CompletableFuture newIncompleteFuture ()
  • CompletableFuture copy ()
  • CompletionStage minimalCompletionStage ()
  • CompletableFuture completeAsync (beszállítói beszállító, végrehajtó végrehajtó)
  • CompletableFuture completeAsync (beszállítói szállító)
  • CompletableFuture orTimeout (hosszú időtúllépés, TimeUnit egység)
  • CompletableFuture completeOnTimeout (T érték, hosszú időtúllépés, TimeUnit egység)

Most már van néhány statikus segédprogramunk is:

  • Végrehajtó késleltetett
  • Végrehajtó késleltetett Végrehajtó (hosszú késleltetés, TimeUnit egység)
  • CompletionStage completeStage (U érték)
  • CompletionStage FailStage (Throwable ex)
  • CompletableFuture sikertelen Future (Throwable ex)

Végül, az időkorlát kezelésére a Java 9 további két új funkciót vezetett be:

  • vagyTimeout ()
  • completeOnTimeout ()

Itt található a részletes cikk további olvasáshoz: Java 9 CompletableFuture API Improvements.

12. Következtetés

Ebben a cikkben ismertettük a módszer módszereit és tipikus felhasználási eseteit CompletableFuture osztály.

A cikk forráskódja elérhető a GitHubon.