Paralelní a Asynchronní Programování v Java 8

Paralelní kód, což je kód, který běží na více než jedno vlákno, byl jednou noční můrou mnoha zkušený vývojář, ale Java 8 přinesl mnoho změn, které by měl tento výkon-posílení trik mnohem více zvládnutelné.

paralelní proudy

před Java 8 byl velký rozdíl mezi paralelním (nebo souběžným) kódem a sekvenčním kódem. Bylo také velmi těžké ladit nesekvenční kód. Jednoduché nastavení bodu zlomu a procházení proudem, jako byste normálně dělali, by odstranilo paralelní aspekt, což je problém, pokud to způsobuje chybu.

naštěstí nám Java 8 dala streamy, největší věc pro vývojáře Java od bean. Pokud nevíte, co to je, Stream API umožňuje zpracovávat sekvence prvků ve funkční hmotě. (Podívejte se na naše Srovnání mezi streamy a.net LINQ zde.) Jednou z výhod streamů je, že struktura kódu zůstává stejná: ať už je to sekvenční nebo souběžné, zůstává stejně čitelné.

Chcete-li, aby váš kód běžel paralelně, jednoduše použijte .parallelStream() místo .stream() (nebo stream.parallel() , pokud nejste tvůrcem streamu).

ale jen proto, že je to snadné, neznamená, že paralelní kód je vždy tou nejlepší volbou. Vždy byste měli zvážit, zda má smysl používat souběžnost pro váš kód. Nejdůležitějším faktorem v tomto rozhodnutí bude rychlost: použijte souběžnost pouze tehdy, pokud váš kód zrychlí než jeho sekvenční protějšek.

rychlostní otázka

paralelní kód získává výhodu rychlosti z použití více vláken namísto jediného, které sekvenční kód používá. Rozhodování o tom, kolik podprocesů vytvořit, může být složitá otázka, protože více podprocesů ne vždy vede k rychlejšímu kódu: pokud použijete příliš mnoho podprocesů, výkon vašeho kódu může ve skutečnosti klesnout.

existuje několik pravidel, která vám řeknou, jaký počet vláken si vybrat. To závisí hlavně na druhu operace, kterou chcete provést, a na počtu dostupných jader.

Výpočet náročných operací, by měly používat počet vláken nižší než nebo se rovná počtu jader, zatímco IO náročných operací, jako je kopírování souborů bez použití PROCESORU, a proto může použít vyšší počet vláken. Kód neví, který případ je použitelný, pokud mu neřeknete, co má dělat. V opačném případě bude výchozí počet vláken rovných počtu jader.

existují dva hlavní případy, kdy může být užitečné spustit kód paralelně místo sekvenčního: časově náročné úkoly a úkoly běžící na velkých sbírkách. Java 8 přinesla nový způsob nakládání s těmito velkými sbírkami, a to s proudy. Proudy mají vestavěnou účinnost leností: používají líné hodnocení, které šetří zdroje tím, že nedělají více, než je nutné. To není totéž jako paralelismus, který se nestará o zdroje, pokud jde rychleji. Takže pro velké sbírky pravděpodobně nepotřebujete klasický paralelismus.

Async

Lekce Z jazyka JavaScript

To je vzácný výskyt, že Java developer lze říci, že se poučili z pohledu na JavaScript, ale když jde o asynchronní programování, JavaScript vlastně má správně jako první. Jako zásadně asynchronní jazyk má JavaScript mnoho zkušeností s tím, jak bolestivé to může být, když je špatně implementováno. Začalo to zpětnými voláními a později bylo nahrazeno sliby. Důležitou výhodou slibů je, že má dva „kanály“: jeden pro data a jeden pro chyby. JavaScript slib by mohl vypadat nějak takhle:

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

Takže, když původní funkce má úspěšný výsledek, f1 se jmenuje, ale je-li chyba byla vyvolána e1 se bude jmenovat. To by mohlo přivést ji zpět na úspěšnou stopu (f2), nebo vést k další chybě (e2). Můžete přejít z datové stopy na chybovou stopu a zpět.

Java verze JavaScriptu promises se nazývá CompletableFuture.

CompletableFuture

CompletableFuture implementuje rozhraní Future a CompletionStage. Future již existoval před Java8, ale sám o sobě nebyl příliš přívětivý pro vývojáře. Můžete jen získat výsledek asynchronní výpočty pomocí .get() způsob, který blokoval zbytek (což asynchronní část docela zbytečné, většinu času), a ty potřebné k realizaci jednotlivých možný scénář ručně. Přidání rozhraní CompletionStage bylo průlomem, díky kterému bylo asynchronní programování v Javě funkční.

CompletionStage je slib, a to slib, že výpočet bude nakonec provedeno. Obsahuje spoustu metod, které vám umožní připojit zpětná volání, která budou provedena při tomto dokončení. Nyní můžeme zvládnout výsledek bez blokování.

Existují dvě hlavní metody, které umožňují spustit asynchronní část kódu: supplyAsync pokud chcete udělat něco s výsledkem metody, a runAsync pokud to nevíte.

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

zpětná Volání

Nyní můžete přidat ty, zpětná volání zvládnout výsledek vaší supplyAsync.

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

.thenApply je podobná funkci .map pro proudy: provádí transformaci. Ve výše uvedeném příkladu vezme výsledek (5)a vynásobí jej 3. Poté tento výsledek (15) předá dále potrubím.

.thenAccept provede metodu výsledku bez jeho transformace. Výsledek také nevrátí. Zde vytiskne“ výsledek je 15 “ na konzoli. Lze jej porovnat s metodou .foreach pro proudy.

.thenRun nepoužívá výsledek asynchronní operace a také nic nevrací, pouze čeká na volání Runnable, dokud nebude dokončen předchozí krok.

Asyncing Async

všechny výše uvedené metody zpětného volání také přicházejí v asynchronní verzi: thenRunAsync, thenApplyAsync atd. Tyto verze mohou běžet na vlastním vlákně a poskytují vám další kontrolu, protože můžete říct, které ForkJoinPool použít.

pokud nepoužíváte asynchronní verzi, budou všechna zpětná volání provedena ve stejném podprocesu.

Když se Věci pokazí,

Když se něco pokazí, exceptionally metoda se používá pro zpracování výjimkou. Můžete mu dát metodu, která vrátí hodnotu, aby se vrátila zpět na datovou stopu, nebo hodit (novou) výjimku.

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

kombinovat a skládat

pomocí metody thenCompose můžete řetězit více CompletableFutures. Bez něj by byl výsledek vnořený CompletableFutures. To dělá thenCompose a thenApply jako flatMap a map pro proudy.

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

Pokud chcete kombinovat výsledkem dvou CompletableFutures, budete potřebovat způsob, pohodlně se nazývá thenCombine.

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

Jak můžete vidět v příkladu výše, výsledek zpětného volání thenCombine mohou být řešeny jako normální CompletableFuture s všechny vaše oblíbené CompletionStage metody.

závěr

paralelní programování již nemusí být nepřekonatelnou překážkou v honbě za rychlejším kódem. Java 8 dělá proces tak přímočarý, jak jen může být, takže jakýkoli kus kódu, který by z něj mohl mít prospěch, lze vytáhnout, kopat a křičet na všechna vlákna, do vícejádrové budoucnosti, která je ve skutečnosti jen současnost. Tím myslím: je to snadné, tak to zkuste a uvidíte jeho výhody pro sebe.



+