Sprievodca po java.util.concurrent.Future

1. Prehľad

V tomto článku sa dozvieme niečo o Budúcnosť. Rozhranie, ktoré existuje od verzie Java 1.5 a môže byť veľmi užitočné pri práci s asynchrónnymi hovormi a súčasným spracovaním.

2. Tvorenie Budúcnosť

Jednoducho povedané Budúcnosť trieda predstavuje budúci výsledok asynchrónneho výpočtu - výsledok, ktorý sa nakoniec objaví v Budúcnosť po dokončení spracovania.

Pozrime sa, ako písať metódy, ktoré vytvárajú a vracajú a Budúcnosť inštancia.

Dlhodobé metódy sú vhodnými kandidátmi na asynchrónne spracovanie a Budúcnosť rozhranie. To nám umožňuje vykonať nejaký iný proces, kým čakáme na úlohu zapuzdrenú v Budúcnosť dokončiť.

Niekoľko príkladov operácií, ktoré by využili asynchronnú povahu Budúcnosť sú:

  • výpočtovo náročné procesy (matematické a vedecké výpočty)
  • manipulácia s veľkými dátovými štruktúrami (veľké dáta)
  • vzdialené volania metód (sťahovanie súborov, šrotovanie HTML, webové služby).

2.1. Implementácia Budúcnosť S FutureTask

Pre náš príklad vytvoríme veľmi jednoduchú triedu, ktorá počíta štvorec s Celé číslo. To rozhodne nespadá do kategórie „dlhodobých“ metód, ale uvedieme a Thread.sleep () zavolajte na to, aby to dokončenie trvalo 1 sekundu:

verejná trieda SquareCalculator {private ExecutorService executor = Executors.newSingleThreadExecutor (); public Future Calculate (Integer input) {return executor.submit (() -> {Thread.sleep (1000); return input * input;}); }}

Bit kódu, ktorý skutočne vykonáva výpočet, je obsiahnutý v hovor () metóda, dodávaná ako výraz lambda. Ako vidíte, na tom nie je nič zvláštne spánok () hovor spomenutý skôr.

Stáva sa zaujímavejším, keď upriamime pozornosť na používanie jazyka Vyvolávateľná a ExecutorService.

Vyvolávateľná je rozhranie predstavujúce úlohu, ktorá vracia výsledok a má jediný hovor () metóda. Tu sme vytvorili jeho inštanciu pomocou výrazu lambda.

Vytvára sa inštancia Vyvolávateľná nás nikam neberie, stále musíme túto inštanciu odovzdať exekútorovi, ktorý sa postará o spustenie tejto úlohy v novom vlákne a vráti nám cenné Budúcnosť objekt. To je kde ExecutorService príde.

Existuje niekoľko spôsobov, ako sa môžeme dostať k ExecutorService väčšinu z nich napríklad poskytuje trieda obslužnosti Exekútori ‘ statické továrenské metódy. V tomto príklade sme použili základné newSingleThreadExecutor (), čo nám dáva ExecutorService schopné pracovať s jedným vláknom naraz.

Akonáhle máme ExecutorService objekt, stačí zavolať Predložiť() míňajúci naše Vyvolávateľná ako argument. Predložiť() sa postará o začatie úlohy a vráti a FutureTask objekt, ktorým je implementácia Budúcnosť rozhranie.

3. Konzumácia Budúcnosť

Až do tohto okamihu sme sa naučili, ako vytvoriť inštanciu Budúcnosť.

V tejto časti sa naučíme pracovať s touto inštanciou preskúmaním všetkých metód, ktorých súčasťou je BudúcnosťAPI.

3.1. Použitím je hotové() a dostať () na získanie výsledkov

Teraz musíme zavolať vypočítať () a použiť vrátené Budúcnosť získať výsledok Celé číslo. Dve metódy z Budúcnosť S touto úlohou nám pomôže API.

Future.isDone () nám hovorí, či exekútor dokončil spracovanie úlohy. Ak je úloha splnená, vráti sa pravda inak sa vráti nepravdivé.

Metóda, ktorá vráti skutočný výsledok z výpočtu, je Future.get (). Všimnite si, že táto metóda blokuje vykonávanie, kým sa úloha nedokončí, ale v našom príklade to nebude problém, pretože najskôr skontrolujeme, či je úloha dokončená volaním je hotové().

Použitím týchto dvoch metód môžeme spustiť ďalší kód, kým budeme čakať na dokončenie hlavnej úlohy:

Budúca budúcnosť = nový SquareCalculator (). Calculate (10); while (! future.isDone ()) {System.out.println ("Výpočet ..."); Závit. Spánok (300); } Výsledok celého čísla = future.get ();

V tomto príklade napíšeme na výstup jednoduchú správu, aby sme používateľa informovali, že program vykonáva výpočet.

Metóda dostať () zablokuje vykonávanie, kým sa úloha nedokončí. Ale nemusíme sa tým báť, pretože náš príklad sa dostane až do bodu, keď dostať () sa volá po skontrolovaní, či je úloha hotová. V tomto scenári teda future.get () sa vždy vráti okamžite.

Za zmienku stojí to dostať () má preťaženú verziu, ktorá vyžaduje časový limit a TimeUnit ako argumenty:

Výsledok celého čísla = future.get (500, TimeUnit.MILLISECONDS);

Rozdiel medzi get (long, TimeUnit) a dostať (), je to, že prvý hodí a Výnimka časového limitu ak sa úloha nevráti pred stanoveným časovým limitom.

3.2. Zrušenie a Budúce Wi Zrušiť()

Predpokladajme, že sme spustili úlohu, ale z nejakého dôvodu nás už výsledok nezaujíma. Môžeme použiť Future.cancel (boolean) povedať exekútorovi, aby zastavil operáciu a prerušil jej základné vlákno:

Budúca budúcnosť = nový SquareCalculator (). Calculate (4); booleovský zrušený = future.cancel (true);

Naša inštancia Budúcnosť z vyššie uvedeného kódu by nikdy nedokončil svoju činnosť. V skutočnosti, ak sa pokúsime dovolať dostať () z tohto prípadu po volaní na Zrušiť(), výsledok by bol a CancellationException. Future.isCancelled () nám povie, či a Budúcnosť už bola zrušená. To môže byť celkom užitočné, ak sa vyhnete získaniu a CancellationException.

Je možné, že hovor na Zrušiť() zlyháva. V takom prípade bude jeho vrátená hodnota nepravdivé. Všimni si Zrušiť() berie a boolovský hodnota ako argument - riadi, či má byť vlákno vykonávajúce túto úlohu prerušené alebo nie.

4. Viac multithreadingu s Závit Bazény

Náš súčasný ExecutorService je jedno vlákno, pretože bol získaný pomocou Executors.newSingleThreadExecutor. Aby sme zvýraznili túto „jednoduchosť“, spustime dva výpočty súčasne:

SquareCalculator squareCalculator = nový SquareCalculator (); Budúca budúcnosť1 = squareCalculator.calculate (10); Budúca budúcnosť2 = squareCalculator.calculate (100); while (! (future1.isDone () && future2.isDone ())) {System.out.println (String.format ("future1 je% s a future2 je% s", future1.isDone ()? "hotovo": "nedokončené", future2.isDone ()? "hotové": "nedokončené")); Závit. Spánok (300); } Celé číslo result1 = future1.get (); Celé číslo result2 = future2.get (); System.out.println (výsledok1 + "a" + výsledok2); squareCalculator.shutdown ();

Teraz poďme analyzovať výstup pre tento kód:

výpočet štvorca pre: 10 future1 sa nekoná a future2 sa nekoná future1 sa nekoná a future2 sa nekoná future1 sa nekoná a future2 sa nekoná future1 sa nekoná a future2 sa nekoná výpočet štvorca pre: 100 future1 je hotové a future2 nie je hotové future1 je hotové a future2 nie je hotové future1 je hotové a future2 nie je hotové 100 a 10 000

Je zrejmé, že tento proces nie je paralelný. Všimnite si, ako sa druhá úloha začína až po dokončení prvej úlohy, takže dokončenie celého procesu trvá asi 2 sekundy.

Aby bol náš program skutočne viacvláknový, mali by sme použiť inú príchuť ExecutorService. Pozrime sa, ako sa zmení správanie nášho príkladu, ak použijeme fond vlákien, ktorý poskytuje továrenská metóda Executors.newFixedThreadPool ():

verejná trieda SquareCalculator {private ExecutorService executor = Executors.newFixedThreadPool (2); // ...}

S jednoduchou zmenou v našom SquareCalculator triedy teraz máme exekútora, ktorý je schopný používať 2 simultánne vlákna.

Ak znova spustíme presne ten istý kód klienta, dostaneme nasledujúci výstup:

výpočet štvorca pre: 10 výpočet štvorca pre: 100 future1 sa nekoná a future2 sa nekoná future1 sa nekoná a future2 sa nekoná future1 sa nekoná a future2 sa nekoná future1 sa nekoná a future2 sa nekoná 100 a 10 000

Teraz to vyzerá oveľa lepšie. Všimnite si, ako sa dve úlohy spúšťajú a končia súčasne, a dokončenie celého procesu trvá asi 1 sekundu.

Existujú aj ďalšie továrenské metódy, ktoré sa dajú použiť na vytvorenie spoločných oblastí vlákien, napríklad Executors.newCachedThreadPool () , ktoré sa používajú opakovane Závits, ak sú k dispozícii, a Executors.newScheduledThreadPool () ktorý naplánuje spustenie príkazov po danom oneskorení.

Pre viac informácií o ExecutorService, prečítajte si náš článok venovaný tejto téme.

5. Prehľad ForkJoinTask

ForkJoinTask je abstraktná trieda, ktorá sa implementuje Budúcnosť a je schopný spustiť veľké množstvo úloh hostených malým počtom skutočných vlákien v systéme ForkJoinPool.

V tejto časti sa chystáme rýchlo pokryť hlavné charakteristiky ForkJoinPool. Komplexného sprievodcu o tejto téme nájdete v našom Sprievodcovi po rozhraní Fork / Join Framework v Jave.

Potom hlavnou charakteristikou a ForkJoinTask je to, že zvyčajne vytvorí nové čiastkové úlohy ako súčasť práce potrebnej na splnenie svojej hlavnej úlohy. Telefonovaním generuje nové úlohy vidlička() a zhromažďuje všetky výsledky s pripojiť sa (), teda názov triedy.

Existujú dve abstraktné triedy, ktoré sa implementujú ForkJoinTask: RecursiveTask ktorá po dokončení vráti hodnotu, a RekurzívnaAkcia ktorá nič nevracia. Ako už z názvu vyplýva, tieto triedy sa majú používať na rekurzívne úlohy, napríklad na navigáciu v súborovom systéme alebo na zložitý matematický výpočet.

Poďme rozšíriť náš predchádzajúci príklad a vytvoriť triedu, ktorá, vzhľadom na Celé číslo, vypočíta štvorcové súčty pre všetky jeho faktoriálne prvky. Napríklad, ak odovzdáme číslo 4 do našej kalkulačky, mali by sme dostať výsledok zo súčtu 4² + 3² + 2² + 1², čo je 30.

Najskôr musíme vytvoriť konkrétnu implementáciu RecursiveTask a implementovať jeho vypočítať () metóda. Tu napíšeme našu obchodnú logiku:

verejná trieda FactorialSquareCalculator rozširuje RecursiveTask {private Integer n; public FactorialSquareCalculator (Celé číslo n) {this.n = n; } @Override chránené Celé číslo výpočet () {if (n <= 1) {návrat n; } Kalkulačka FactorialSquareCalculator = nový FactorialSquareCalculator (n - 1); calculator.fork (); návrat n * n + calculator.join (); }}

Všimnite si, ako dosiahneme rekurzivitu vytvorením novej inštancie FactorialSquareCalculator v rámci vypočítať (). Volaním vidlička(), neblokujúca metóda, pýtame sa ForkJoinPool na spustenie vykonania tejto podúlohy.

The pripojiť sa () metóda vráti výsledok z tohto výpočtu, ku ktorému pripočítame druhú mocninu čísla, ktoré práve navštevujeme.

Teraz už len musíme vytvoriť ForkJoinPool zvládnuť vykonávanie a správu vlákien:

ForkJoinPool forkJoinPool = nový ForkJoinPool (); FactorialSquareCalculator kalkulačka = nový FactorialSquareCalculator (10); forkJoinPool.execute (kalkulačka);

6. Záver

V tomto článku sme mali komplexný pohľad na Budúcnosť navštívi všetky jeho metódy. Tiež sme sa naučili, ako využiť silu vláknových fondov na spustenie viacerých paralelných operácií. Hlavné metódy z ForkJoinTask trieda, vidlička() a pripojiť sa () boli tiež krátko zakryté.

Máme mnoho ďalších skvelých článkov o paralelných a asynchrónnych operáciách v Jave. Tu sú tri z nich, ktoré úzko súvisia s Budúcnosť rozhranie (niektoré z nich sú už spomenuté v článku):

  • Sprievodca po CompletableFuture - implementácia Budúcnosť s mnohými ďalšími funkciami zavedenými v prostredí Java 8
  • Sprievodca po rozhraní Fork / Join Framework v Jave - viac o ForkJoinTask sme sa venovali v časti 5
  • Sprievodca po Jave ExecutorService - venovaný ExecutorService rozhranie

Skontrolujte zdrojový kód použitý v tomto článku v našom úložisku GitHub.


$config[zx-auto] not found$config[zx-overlay] not found