Ako spustiť vlákno v Jave

1. Úvod

V tomto výučbe sa chystáme preskúmať rôzne spôsoby spustenia vlákna a vykonávania paralelných úloh.

To je veľmi užitočné, najmä keď pracujete s dlhými alebo opakujúcimi sa operáciami, ktoré nemôžu bežať na hlavnom vlákne, alebo kde interakciu používateľského rozhrania nemožno pozastaviť počas čakania na výsledky operácie.

Ak sa chcete dozvedieť viac podrobností o vláknach, určite si prečítajte náš návod o životnom cykle vlákna v jazyku Java.

2. Základy fungovania vlákna

Logiku, ktorá beží v paralelnom vlákne, môžeme ľahko napísať pomocou znaku Závit rámec.

Skúsme základný príklad rozšírením Závit trieda:

public class NewThread extends Thread {public void run () {long startTime = System.currentTimeMillis (); int i = 0; while (true) {System.out.println (this.getName () + ": Je spustené nové vlákno ..." + i ++); skúste {// Počkajte jednu sekundu, aby sa netlačila príliš rýchlo Thread.sleep (1000); } catch (InterruptedException e) {e.printStackTrace (); } ...}}}

A teraz napíšeme druhú triedu na inicializáciu a spustenie nášho vlákna:

public class SingleThreadExample {public static void main (String [] args) {NewThread t = new NewThread (); t.start (); }}

Mali by sme zavolať štart () metóda na vláknach v NOVÝ stav (ekvivalent nezačatého). V opačnom prípade Java vloží inštanciu IllegalThreadStateException výnimkou.

Teraz predpokladajme, že musíme spustiť viac vlákien:

verejná trieda MultipleThreadsExample {public static void main (String [] args) {NewThread t1 = new NewThread (); t1.setName ("MyThread-1"); NewThread t2 = nový NewThread (); t2.setName ("MyThread-2"); t1.start (); t2.start (); }}

Náš kód stále vyzerá celkom jednoducho a veľmi podobne ako príklady, ktoré môžeme nájsť online.

Samozrejme, toto je ďaleko od kódu pripraveného na výrobu, kde je mimoriadne dôležité správne spravovať zdroje, aby sa zabránilo príliš veľkému prepínaniu kontextu alebo nadmernému využívaniu pamäte.

Aby sme boli pripravení na výrobu, musíme teraz napísať ďalší štandardný štítok zaoberať sa s:

  • dôsledné vytváranie nových vlákien
  • počet súbežných živých vlákien
  • rozmiestnenie vlákien: veľmi dôležité pre vlákna démona, aby sa zabránilo únikom

Ak chceme, môžeme napísať vlastný kód pre všetky tieto prípadové scenáre a dokonca aj pre niektoré ďalšie, ale prečo by sme mali znova objavovať koleso?

3. The ExecutorService Rámca

The ExecutorService implementuje návrhový vzor Thread Pool (tiež sa nazýva model replikovaného pracovníka alebo pracovníka a posádky) a stará sa o správu vlákien, ktorú sme spomenuli vyššie, a pridáva niektoré veľmi užitočné funkcie, ako sú opätovné použitie vlákien a fronty úloh.

Veľmi dôležitá je najmä opätovná použiteľnosť vlákna: v aplikáciách vo veľkom meradle vytvára alokácia a deallokacia mnohých objektov vlákien významnú réžiu správy pamäte.

S pracovnými vláknami minimalizujeme réžiu spôsobenú vytvorením vlákna.

Na uľahčenie konfigurácie bazéna ExecutorService prichádza s jednoduchým konštruktorom a niektorými možnosťami prispôsobenia, ako napríklad typ frontu, minimálny a maximálny počet vlákien a ich konvencia pomenovania.

Pre viac informácií o Exekútorská služba, prečítajte si nášho Sprievodcu službou Java ExecutorService.

4. Založenie úlohy s exekútormi

Vďaka tomuto výkonnému rámci môžeme zmeniť naše myslenie od začatia vlákien k zadávaniu úloh.

Pozrime sa, ako môžeme odoslať asynchrónnu úlohu nášmu exekútorovi:

ExecutorService vykonávateľ = Executors.newFixedThreadPool (10); ... executor.submit (() -> {new Task ();});

Môžeme použiť dve metódy: vykonať, ktorý nevracia nič, a Predložiť, ktorá vracia a Budúcnosť zapuzdrenie výsledku výpočtu.

Pre viac informácií o Futures, prečítajte si nášho Sprievodcu java.util.concurrent.Future.

5. Spustenie úlohy pomocou CompletableFutures

Na získanie konečného výsledku z a Budúcnosť objekt, ktorý môžeme použiť dostať metóda dostupná v objekte, ale to by blokovalo nadradené vlákno až do konca výpočtu.

Prípadne by sme sa bloku mohli vyhnúť pridaním ďalšej logiky k našej úlohe, musíme však zvýšiť zložitosť nášho kódu.

Java 1.8 predstavila nový rámec nad Budúcnosť konštrukt na lepšiu prácu s výsledkom výpočtu: CompletableFuture.

CompletableFuture náradie CompletableStage, ktorá pridáva obrovský výber metód na pripojenie spätných volaní a na vylúčenie všetkých inštalatérskych prác potrebných na spustenie operácií s výsledkom, keď je hotový.

Implementácia na zadanie úlohy je oveľa jednoduchšia:

CompletableFuture.supplyAsync (() -> „Dobrý deň“);

supplyAsync berie a Dodávateľ obsahujúci kód, ktorý chceme vykonať asynchrónne - v našom prípade parameter lambda.

Úloha je teraz implicitne predložená ForkJoinPool.commonPool (), alebo môžeme určiť Exekútor uprednostňujeme ako druhý parameter.

Ak chcete vedieť viac o CompletableFuture, prosím, prečítajte si nášho Sprievodcu po CompletableFuture.

6. Spúšťanie oneskorených alebo pravidelných úloh

Pri práci so zložitými webovými aplikáciami bude možno potrebné vykonať úlohy v konkrétnom čase, možno pravidelne.

Java má niekoľko nástrojov, ktoré nám môžu pomôcť spustiť oneskorené alebo opakujúce sa operácie:

  • java.util.Timer
  • java.util.concurrent.ScheduledThreadPoolExecutor

6.1. Časovač

Časovač je zariadenie na plánovanie úloh na budúce vykonávanie v pozadí vlákna.

Úlohy môžu byť naplánované na jednorazové vykonanie alebo na opakované vykonanie v pravidelných intervaloch.

Pozrime sa, ako vyzerá kód, ak chceme spustiť úlohu po jednej sekunde oneskorenia:

TimerTask task = new TimerTask () {public void run () {System.out.println ("Úloha vykonaná dňa:" + nový Date () + "n" + "Názov vlákna:" + Thread.currentThread (). GetName ( )); }}; Časovač časovača = nový časovač ("Časovač"); dlhé oneskorenie = 1000L; timer.schedule (task, delay);

Teraz pridajme opakujúci sa rozvrh:

timer.scheduleAtFixedRate (repeatTask, delay, period);

Tentokrát bude úloha spustená po zadanom oneskorení a bude sa opakovať po uplynutí časového obdobia.

Ďalšie informácie nájdete v našom sprievodcovi programom Java Timer.

6.2. ScheduledThreadPoolExecutor

ScheduledThreadPoolExecutor má podobné metódy ako Časovač trieda:

ScheduledExecutorService executorService = Executors.newScheduledThreadPool (2); ScheduledFuture resultFuture = executorService.schedule (callableTask, 1, TimeUnit.SECONDS);

Na záver nášho príkladu používame scheduleAtFixedRate () pre opakujúce sa úlohy:

ScheduledFuture resultFuture = executorService.scheduleAtFixedRate (runnableTask, 100, 450, TimeUnit.MILLISECONDS);

Vyššie uvedený kód vykoná úlohu po počiatočnom oneskorení 100 milisekúnd, a potom vykoná rovnakú úlohu každých 450 milisekúnd.

Ak procesor nemôže dokončiť spracovanie úlohy včas pred ďalším výskytom, znak ScheduledExecutorService počká, kým sa aktuálna úloha nedokončí, a potom začne ďalšiu.

Aby sme sa vyhli tejto čakacej dobe, môžeme použiť scheduleWithFixedDelay (), ktorý, ako je popísané v jeho názve, zaručuje pevné časové oneskorenie medzi iteráciami úlohy.

Pre viac informácií o ScheduledExecutorService, prečítajte si nášho Sprievodcu službou Java ExecutorService.

6.3. Ktorý nástroj je lepší?

Ak spustíme vyššie uvedené príklady, výsledok výpočtu vyzerá rovnako.

Takže ako vyberieme ten správny nástroj?

Keď rámec ponúka viac možností, je dôležité porozumieť základnej technológii, aby ste mohli urobiť informované rozhodnutie.

Skúsme sa ponoriť trochu hlbšie pod kapotu.

Časovač:

  • neponúka záruky v reálnom čase: plánuje úlohy pomocou Object.wait (dlhé) metóda
  • existuje jedno vlákno na pozadí, takže úlohy sa spúšťajú postupne a dlhotrvajúca úloha môže ostatných zdržať
  • runtime výnimky vyvolané v a TimerTask zabije jediné dostupné vlákno, a tým zabije Časovač

ScheduledThreadPoolExecutor:

  • je možné nakonfigurovať s ľubovoľným počtom vlákien
  • môže využívať výhody všetkých dostupných jadier CPU
  • zachytáva výnimky za behu a umožňuje nám ich zvládnuť, ak chceme (prepísaním afterExecute metóda z ThreadPoolExecutor)
  • zruší úlohu, ktorá spôsobila výnimku, a nechá ostatných pokračovať v behu
  • spolieha sa na systém plánovania OS, ktorý sleduje časové pásma, meškania, slnečný čas atď.
  • poskytuje rozhranie API pre spoluprácu, ak potrebujeme koordináciu medzi viacerými úlohami, napríklad čakanie na dokončenie všetkých zadaných úloh
  • poskytuje lepšie API pre správu životného cyklu vlákna

Voľba je teraz zrejmá, však?

7. Rozdiel medzi Budúcnosť a Naplánovaná budúcnosť

V našich príkladoch kódu to môžeme pozorovať ScheduledThreadPoolExecutor vráti konkrétny typ súboru Budúcnosť: Naplánovaná budúcnosť.

Naplánovaná budúcnosť rozširuje oboje Budúcnosť a Oneskorené rozhrania, čím zdedil ďalšiu metódu getDelay ktorý vráti zostávajúce oneskorenie spojené s aktuálnou úlohou. Je predĺžená o RunnableScheduledFuture ktorá pridáva metódu na kontrolu, či je úloha pravidelná.

ScheduledThreadPoolExecutor implementuje všetky tieto konštrukty prostredníctvom vnútornej triedy ScheduledFutureTask a používa ich na riadenie životného cyklu úlohy.

8. Závery

V tejto príručke sme experimentovali s rôznymi dostupnými rámcami na spúšťanie vlákien a paralelné spúšťanie úloh.

Potom sme išli hlbšie do rozdielov medzi nimi Časovač a ScheduledThreadPoolExecutor.

Zdrojový kód článku je k dispozícii na GitHub.


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