párhuzamos és aszinkron programozás Java 8-ban

a párhuzamos kód, amely egynél több szálon futó kód, egykor sok tapasztalt fejlesztő rémálma volt, de a Java 8 sok olyan változást hozott, amelyek sokkal kezelhetőbbé teszik ezt a teljesítménynövelő trükköt.

párhuzamos adatfolyamok

a Java 8 előtt nagy különbség volt a párhuzamos (vagy egyidejű) kód és a szekvenciális kód között. Nagyon nehéz volt a nem szekvenciális kód hibakeresése is. Ha egyszerűen beállítunk egy töréspontot, és átmegyünk az áramláson, mint általában, akkor eltávolítjuk a párhuzamos aspektust, ami probléma, ha ez okozza a hibát.

szerencsére a Java 8 patakokat adott nekünk, a Java fejlesztők számára a legnagyobb dolog a bean óta. Ha nem tudja, mik azok, a Stream API lehetővé teszi az elemek sorozatainak kezelését egy funkcionális kérdésben. (Nézze meg a streamek és a. net LINQ közötti összehasonlítást itt.) A patakok egyik előnye, hogy a kód felépítése változatlan marad: függetlenül attól, hogy szekvenciális vagy egyidejű, ugyanolyan olvasható marad.

a kód párhuzamos futtatásához egyszerűen a .parallelStream() – et használja a .stream() helyett (vagy stream.parallel(), ha nem te vagy az adatfolyam létrehozója).

de csak azért, mert könnyű, nem jelenti azt, hogy a párhuzamos kód mindig a legjobb választás. Mindig mérlegelnie kell, hogy van-e értelme a párhuzamosságot használni a kóddarab számára. A döntés legfontosabb tényezője a sebesség lesz: csak akkor használja a párhuzamosságot, ha ez gyorsabbá teszi a kódot, mint a szekvenciális megfelelője.

a sebesség kérdés

párhuzamos kód lesz a sebesség előnye a több szál helyett az egyetlen, hogy szekvenciális kód használ. Annak eldöntése, hogy hány szálat hozzon létre, trükkös kérdés lehet, mert több szál nem mindig eredményez gyorsabb kódot: ha túl sok szálat használ, akkor a kód teljesítménye valóban csökkenhet.

van néhány szabály, amely megmondja, hogy hány szálat válasszon. Ez leginkább a végrehajtani kívánt művelet típusától és a rendelkezésre álló magok számától függ.

a számításigényes műveleteknek a magok számánál kisebb vagy azzal egyenlő számú szálat kell használniuk, míg az IO-intenzív műveleteknek, mint például a fájlok másolása, nincs hasznuk a CPU számára, ezért nagyobb számú szálat használhatnak. A kód nem tudja, melyik eset alkalmazható, hacsak nem mondja meg neki, mit kell tennie. Ellenkező esetben alapértelmezés szerint a szálak száma megegyezik a magok számával.

két fő eset van, amikor hasznos lehet A kód párhuzamos futtatása a szekvenciális helyett: időigényes feladatok és a feladatok nagy gyűjteményeken futnak. A Java 8 új módszert hozott a nagy gyűjtemények kezelésére, nevezetesen patakokkal. A patakok lustasággal beépített hatékonysággal rendelkeznek: lusta értékelést használnak, amely erőforrásokat takarít meg azzal, hogy nem tesz többet a szükségesnél. Ez nem ugyanaz, mint a párhuzamosság, amely nem törődik az erőforrásokkal, amíg gyorsabban megy. Tehát nagy gyűjteményekhez valószínűleg nincs szüksége klasszikus párhuzamosságra.

Aszinkronizálás

Lessons From JavaScript

ritkán fordul elő, hogy egy Java fejlesztő azt mondja, hogy tanult valamit a JavaScript-ből, de amikor az aszinkron programozásról van szó, a JavaScript valójában először helyesen tette. Alapvetően aszinkron nyelvként a JavaScript sok tapasztalattal rendelkezik arról, hogy milyen fájdalmas lehet, ha rosszul hajtják végre. A visszahívásokkal kezdődött, majd később ígéretek váltották fel. Az ígéretek fontos előnye, hogy két “csatornája” van: az egyik az adatokhoz, a másik a hibákhoz. A JavaScript ígéret valahogy így nézhet ki:

func.then(f1).catch(e1).then(f2).catch(e2);

tehát, ha az eredeti függvény sikeres eredményt ad, az f1-et hívják, de ha hibát dobtak, az e1-et hívják. Ez visszahozhatja a sikeres pályára (f2), vagy újabb hibát (e2) eredményezhet. Lehet menni az adatok nyomon követését hiba nyomon követését, majd vissza.

a Java változata JavaScript ígéretek hívják CompletableFuture.

CompletableFuture

CompletableFuture mind a Future, mind a CompletionStage interfészt megvalósítja. Future már létezett a Java8 előtt, de önmagában nem volt túl fejlesztőbarát. Az aszinkron számítás eredményét csak a .get() módszerrel kaphatja meg, amely a többit blokkolta (az aszinkron rész legtöbbször értelmetlenné vált), és minden lehetséges forgatókönyvet manuálisan kellett végrehajtania. A CompletionStage interfész hozzáadása volt az áttörés, amely az aszinkron programozást a Java-ban működőképessé tette.

CompletionStage egy ígéret, nevezetesen az ígéret, hogy a számítás végül megtörténik. Tartalmaz egy csomó módszert, amelyek lehetővé teszik a visszahívások csatolását, amelyek végrehajtásra kerülnek ezen a befejezésen. Most blokkolás nélkül tudjuk kezelni az eredményt.

két fő módszer van, amellyel elindíthatja a kód aszinkron részét: supplyAsync ha valamit szeretne tenni a módszer eredményével, és runAsync ha nem.

CompletableFuture.runAsync(() → System.out.println("Run async in completable future " + Thread.currentThread()));CompletableFuture.supplyAsync(() → 5);

visszahívások

most hozzáadhatja ezeket a visszahívásokat a supplyAsync eredményének kezeléséhez.

CompletableFuture.supplyAsync(() → 5).thenApply(i → i * 3).thenAccept(i → System.out.println("The result is " + i).thenRun(() → System.out.println("Finished."));

.thenApply hasonló a patakok .map függvényéhez: átalakítást hajt végre. A fenti példában az (5) eredményt megszorozzuk 3-mal. Ezután átadja ezt az eredményt (15) tovább a csövön.

.thenAccept egy módszert hajt végre az eredményen anélkül, hogy átalakítaná. Ez szintén nem fog eredményt adni. Itt kinyomtatja az” eredmény 15 ” – et a konzolra. A patakok .foreach módszerével hasonlítható össze.

.thenRun nem használja az aszinkron művelet eredményét, és nem is ad vissza semmit, csak megvárja a Runnable hívását, amíg az előző lépés befejeződik.

az aszinkron Aszinkronizálása

a fenti visszahívási módszerek mindegyike aszinkron változatban is elérhető: thenRunAsync, thenApplyAsync stb. Ezek a verziók futhatnak a saját szálukon, és extra vezérlést biztosítanak, mert meg tudja mondani, hogy melyik ForkJoinPool – et használja.

ha nem használja az aszinkron verziót, akkor a visszahívások mind ugyanazon a szálon kerülnek végrehajtásra.

amikor a dolgok rosszul mennek

ha valami rosszul megy, a exceptionally módszert használják a kivétel kezelésére. Megadhat egy olyan módszert, amely visszaad egy értéket, hogy visszatérjen az adatsávra, vagy dobjon egy (új) kivételt.

….exceptionally(ex → new Foo()).thenAccept(this::bar);

kombinálja és komponálja

a CompletableFutures többszörös láncolása a thenCompose módszerrel lehetséges. Enélkül az eredmény beágyazódna CompletableFutures. Ez teszi thenCompose és thenApply mint flatMap és map a stream.

CompletableFuture.supplyAsync(() -> "Hello").thenCompose(s -> CompletableFuture.supplyAsync(() -> s + "World"));

ha két CompletableFutures eredményét szeretné kombinálni, akkor szüksége lesz egy kényelmesen nevezett módszerre thenCombine.

future.thenCombine(future2, Integer::sum).thenAccept(value → System.out.println(value));

mint látható a fenti példában, az eredmény a visszahívás thenCombine lehet kezelni, mint egy normál CompletableFuture az összes kedvenc CompletionStage módszerek.

következtetés

a párhuzamos programozásnak már nem kell leküzdhetetlen akadálynak lennie a gyorsabb kód vadászatában. A Java 8 a lehető legegyszerűbbé teszi a folyamatot, így minden olyan kódrészletet, amely esetleg profitálhat belőle, minden szálon át lehet húzni, rúgni és sikoltozni a többmagos jövőbe, amely valójában csak a mai nap. Ez alatt azt értem: könnyű megtenni, ezért próbáld ki, és nézd meg magadnak az előnyeit.



+