Sprievodca službou Java ExecutorService

1. Prehľad

ExecutorService je rámec poskytovaný JDK, ktorý zjednodušuje vykonávanie úloh v asynchrónnom režime. Všeobecne povedané, ExecutorService automaticky poskytuje skupinu vlákien a API na priradenie úloh k nej.

2. Vytvorenie inštancie ExecutorService

2.1. Továrne metódy Exekútori Trieda

Najjednoduchší spôsob tvorby ExecutorService je použitie jednej z továrenských metód Exekútori trieda.

Napríklad nasledujúci riadok kódu vytvorí vláknový fond s 10 vláknami:

ExecutorService vykonávateľ = Executors.newFixedThreadPool (10);

Existuje niekoľko ďalších továrenských metód na vytvorenie preddefinovaných ExecutorService ktoré vyhovujú konkrétnym prípadom použitia. Najlepšie riešenie pre svoje potreby nájdete v oficiálnej dokumentácii spoločnosti Oracle.

2.2. Priamo vytvorte ExecutorService

Pretože ExecutorService je rozhranie, je možné použiť inštanciu akýchkoľvek jeho implementácií. Existuje niekoľko implementácií, z ktorých si môžete vybrať v java.util.concurrent balíček alebo si môžete vytvoriť svoj vlastný.

Napríklad ThreadPoolExecutor trieda má niekoľko konštruktorov, ktoré možno použiť na konfiguráciu služby vykonávateľa a jej vnútorného fondu.

ExecutorService executorService = nový ThreadPoolExecutor (1, 1, 0L, TimeUnit.MILLISECONDS, nový LinkedBlockingQueue ());

Môžete si všimnúť, že vyššie uvedený kód je veľmi podobný zdrojovému kódu výrobnej metódy newSingleThreadExecutor (). Vo väčšine prípadov nie je potrebná podrobná manuálna konfigurácia.

3. Priraďovanie úloh k ExecutorService

ExecutorService môže vykonať Spustiteľné a Vyvolávateľná úlohy. Aby sme v tomto článku zjednodušili prácu, použijú sa dve primitívne úlohy. Všimnite si, že sa tu namiesto anonymných vnútorných tried používajú výrazy lambda:

Runnable runnableTask = () -> {try {TimeUnit.MILLISECONDS.sleep (300); } catch (InterruptedException e) {e.printStackTrace (); }}; Vyvolateľná callableTask = () -> {TimeUnit.MILLISECONDS.sleep (300); návrat "Vykonanie úlohy"; }; Zoznam callableTasks = new ArrayList (); callableTasks.add (callableTask); callableTasks.add (callableTask); callableTasks.add (callableTask);

Úlohy je možné priradiť k ExecutorService pomocou niekoľkých metód, vrátane vykonať (), ktorá sa dedí z Exekútor rozhranie a tiež Predložiť(), invokeAny (), invokeAll ().

The vykonať () metóda je neplatný, a nedáva žiadnu možnosť získať výsledok vykonania úlohy alebo skontrolovať stav úlohy (či je spustená alebo vykonaná).

executorService.execute (runnableTask);

Predložiť() predkladá a Vyvolávateľná alebo a Spustiteľné úloha do ExecutorService a vráti výsledok typu Budúcnosť.

Budúca budúcnosť = executorService.submit (callableTask);

invokeAny () priradí zbierku úloh k Exekútorská služba, spôsobí vykonanie každého z nich a vráti výsledok úspešného vykonania jednej úlohy (ak došlo k úspešnému vykonaniu).

Výsledok reťazca = executorService.invokeAny (callableTasks);

invokeAll () priradí zbierku úloh k Exekútorská služba, spôsobí vykonanie každého z nich a vráti výsledok všetkých vykonaní úloh vo forme zoznamu objektov typu Budúcnosť.

Zoznam futures = executorService.invokeAll (callableTasks);

Teraz, predtým, ako pôjdeme ďalej, je potrebné prediskutovať ďalšie dve veci: vypnutie an ExecutorService a riešenie Budúcnosť návratové typy.

4. Vypnutie a ExecutorService

Všeobecne platí, že ExecutorService nebudú automaticky zničené, keď nebude potrebné spracovať žiadnu úlohu. Zostane nažive a bude čakať na novú prácu.

V niektorých prípadoch je to veľmi užitočné; napríklad ak aplikácia potrebuje na spracovanie úloh, ktoré sa objavujú nepravidelne, alebo množstvo týchto úloh nie je v čase kompilácie známe.

Na druhej strane by aplikácia mohla dosiahnuť svoj koniec, ale nezastaví sa kvôli čakaniu ExecutorService spôsobí, že JVM bude stále bežať.

Ak chcete správne vypnúť ExecutorService, máme vypnúť() a shutdownNow () API.

The vypnúť()metóda nespôsobuje okamžité zničenie ExecutorService. Urobí to ExecutorService prestať prijímať nové úlohy a vypnúť po dokončení všetkých spustených vlákien ich súčasnú prácu.

executorService.shutdown ();

The shutdownNow () metóda sa snaží zničiť ExecutorService okamžite, ale nezaručuje to, že všetky spustené vlákna budú zastavené súčasne. Táto metóda vracia zoznam úloh, ktoré čakajú na spracovanie. Je na vývojárovi, aby rozhodol, čo s týmito úlohami urobí.

Zoznam notExecutedTasks = executorService.shutDownNow ();

Jeden dobrý spôsob, ako vypnúť ExecutorService (ktoré tiež odporúča spoločnosť Oracle) je použiť obidve tieto metódy v kombinácii s awaitTermination () metóda. Týmto prístupom ExecutorService najskôr prestane prijímať nové úlohy a potom počká do stanoveného časového obdobia na dokončenie všetkých úloh. Ak tento čas vyprší, vykonávanie sa okamžite zastaví:

executorService.shutdown (); try {if (! executorService.awaitTermination (800, TimeUnit.MILLISECONDS)) {executorService.shutdownNow (); }} catch (InterruptedException e) {executorService.shutdownNow (); }

5. Budúcnosť Rozhranie

The Predložiť() a invokeAll () metódy vracajú objekt alebo kolekciu objektov typu Budúcnosť, čo nám umožňuje získať výsledok vykonania úlohy alebo skontrolovať stav úlohy (či je spustená alebo vykonaná).

The Budúcnosť rozhranie poskytuje špeciálnu metódu blokovania dostať () ktorý vráti skutočný výsledok Vyvolávateľná vykonanie úlohy resp nulový v prípade Spustiteľné úloha. Volá sa dostať () metóda, keď je úloha stále spustená, spôsobí zablokovanie vykonávania, kým sa úloha správne nevykoná a kým nebude k dispozícii výsledok.

Budúca budúcnosť = executorService.submit (callableTask); Výsledok reťazca = null; skúste {výsledok = future.get (); } catch (InterruptedException | ExecutionException e) {e.printStackTrace (); }

S veľmi dlhým blokovaním spôsobeným dostať () metódou sa môže výkon aplikácie zhoršiť. Ak výsledné údaje nie sú rozhodujúce, je možné vyhnúť sa takýmto problémom pomocou časových limitov:

Výsledok reťazca = future.get (200, TimeUnit.MILLISECONDS);

Ak je doba vykonávania dlhšia, ako je uvedené (v tomto prípade 200 milisekúnd), a Výnimka časového limitu bude vyhodený.

The je hotové() metódou je možné skontrolovať, či je priradená úloha už spracovaná alebo nie.

The Budúcnosť Rozhranie tiež umožňuje zrušenie vykonania úlohy s Zrušiť() metódou a skontrolovať zrušenie pomocou isCancelled () metóda:

booleovský zrušený = future.cancel (true); boolean isCancelled = future.isCancelled ();

6. ScheduledExecutorService Rozhranie

The ScheduledExecutorService spúšťa úlohy po určitom preddefinovanom oneskorení a / alebo pravidelne. Opäť najlepší spôsob, ako vytvoriť inštanciu a ScheduledExecutorService je použitie továrenských metód Exekútori trieda.

Pre tento oddiel a ScheduledExecutorService s jedným vláknom budú použité:

ScheduledExecutorService executorService = Exekútori .newSingleThreadScheduledExecutor ();

Ak chcete naplánovať vykonanie jednej úlohy po pevnom oneskorení, použite nás naplánovaný() metóda ScheduledExecutorService. Existujú dva naplánovaný() metódy, ktoré umožňujú vykonávanie Spustiteľné alebo Vyvolávateľná úlohy:

Budúci výsledokFutúra = executorService.schedule (callableTask, 1, TimeUnit.SECONDS);

The scheduleAtFixedRate () metóda umožňuje vykonávať úlohu pravidelne po stanovenom oneskorení. Vyššie uvedený kód sa oneskorí o jednu sekundu pred vykonaním callableTask.

Nasledujúci blok kódu vykoná úlohu po počiatočnom oneskorení 100 milisekúnd a potom vykoná rovnakú úlohu každých 450 milisekúnd. Ak procesor potrebuje na vykonanie priradenej úlohy viac času ako obdobie parameter parametra scheduleAtFixedRate () metóda, ScheduledExecutorService počká, kým sa aktuálna úloha dokončí, pred spustením ďalšej:

Budúci výsledokFutúra = služba .scheduleAtFixedRate (runnableTask, 100, 450, TimeUnit.MILLISECONDS);

Ak je potrebné mať medzi iteráciami úlohy pevné časové oneskorenie, scheduleWithFixedDelay () by sa mali používať. Napríklad nasledujúci kód zaručí 150-milisekundovú pauzu medzi koncom aktuálneho spustenia a začiatkom iného.

service.scheduleWithFixedDelay (úloha, 100, 150, TimeUnit.MILLISECONDS);

Podľa scheduleAtFixedRate () a scheduleWithFixedDelay () Metodické zmluvy sa perióda vykonania úlohy skončí ukončením ExecutorService alebo ak je počas vykonávania úlohy vyvolaná výnimka.

7. ExecutorService vs. Fork / Pripojiť sa

Po vydaní Java 7 sa veľa vývojárov rozhodlo, že ExecutorService framework by mal byť nahradený fork / join framework. Nie vždy to však je správne rozhodnutie. Napriek jednoduchosti použitia a častému zvyšovaniu výkonu spojenému s fork / join, dochádza tiež k zníženiu miery kontroly vývojárom nad súčasným vykonávaním.

ExecutorService dáva vývojárovi možnosť ovládať počet vygenerovaných vlákien a granularitu úloh, ktoré by sa mali vykonávať samostatnými vláknami. Najlepší prípad použitia pre ExecutorService je spracovanie nezávislých úloh, ako sú transakcie alebo žiadosti podľa schémy „jedno vlákno pre jednu úlohu“.

Naproti tomu podľa dokumentácie Oracle bol fork / join navrhnutý tak, aby urýchlil prácu, ktorú je možné rekurzívne rozdeliť na menšie kúsky.

8. Záver

Aj napriek relatívnej jednoduchosti ExecutorService, existuje niekoľko bežných nástrah. Poďme si ich zhrnúť:

Udržiavanie nepoužitého ExecutorService nažive: V časti 4 tohto článku je podrobné vysvetlenie, ako vypnúť počítač ExecutorService;

Nesprávna kapacita skupiny vlákien pri použití skupiny vlákien s pevnou dĺžkou: Je veľmi dôležité určiť, koľko vlákien bude aplikácia potrebovať na efektívne vykonávanie úloh. Súbor vlákien, ktorý je príliš veľký, spôsobí zbytočné režijné náklady len na vytvorenie vlákien, ktoré budú väčšinou v režime čakania. Príliš málo aplikácií môže spôsobiť, že aplikácia nereaguje z dôvodu dlhého čakania na úlohy vo fronte;

Volanie a Budúcnosť‘S dostať () metóda po zrušení úlohy: Pokus o získanie výsledku už zrušenej úlohy spustí a CancellationException.

Neočakávane dlhé blokovanie pomocou Budúcnosť‘S dostať () metóda: Mali by ste použiť časové limity, aby ste sa vyhli neočakávanému čakaniu.

Kód tohto článku je k dispozícii v úložisku GitHub.


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