Programovanie

Moderné vlákna: primer Java súbežnosti

Veľa z toho, čo sa dá naučiť o programovaní pomocou vlákien Java, sa počas vývoja platformy Java dramaticky nezmenilo, ale zmenilo sa to postupne. V tomto priméri vlákien Java Cameron Laird zasiahne niektoré z vysokých (a nízkych) bodov vlákien ako techniku ​​súbežného programovania. Získajte prehľad toho, čo je na viacvláknových programovaniach neustále náročné a zistite, ako sa platforma Java vyvinula, aby splnila niektoré z výziev.

Súbežnosť patrí medzi najväčšie starosti nováčikov v programovaní v jazyku Java, ale nie je dôvod, aby ste sa tým nechali vystrašiť. K dispozícii je nielen vynikajúca dokumentácia (v tomto článku preskúmame niekoľko zdrojov), ale s vývojom platformy Java sa ľahšie pracuje s vláknami Java. Na to, aby ste sa naučili, ako programovať viacvláknové programovanie v jazykoch Java 6 a 7, skutočne potrebujete iba niektoré stavebné bloky. Začneme týmito:

  • Jednoduchý závitový program
  • Vlákanie je hlavne o rýchlosti, však?
  • Výzvy súbežnosti Javy
  • Kedy použiť Runnable
  • Keď sa dobré vlákna pokazia
  • Čo je nové v prostredí Java 6 a 7
  • Čo ďalej s vláknami Java

Tento článok je prieskumom začiatočníckych techník vlákien Java vrátane odkazov na niektoré z najčastejšie čítaných úvodných článkov JavaWorld o programovaní viacerých vlákien. Naštartujte svoje motory a postupujte podľa odkazov vyššie, ak ste pripravení začať sa dnes učiť o vláknach Java.

Jednoduchý závitový program

Zvážte nasledujúci zdroj Java.

Zoznam 1. FirstThreadingExample

trieda FirstThreadingExample {public static void main (String [] args) {// Druhým argumentom je oneskorenie medzi // postupnými výstupmi. Oneskorenie sa // meria v milisekundách. „10“, napríklad //, znamená, „vytlačiť riadok každú // stotinu sekundy“. ExampleThread mt = nový ExampleThread ("A", 31); ExampleThread mt2 = nový ExampleThread ("B", 25); ExampleThread mt3 = nový ExampleThread ("C", 10); mt.start (); mt2.start (); mt3.start (); }} trieda ExampleThread predlžuje vlákno {súkromné ​​int oneskorenie; public ExampleThread (String label, int d) {// Dajte tomuto konkrétnemu vláknu // názov: "vlákno 'LABEL'". super ("vlákno" "+ štítok +" ""); oneskorenie = d; } public void run () {for (int count = 1, row = 1; row <20; row ++, count ++) {try {System.out.format ("Line #% d from% s \ n", count, getName ()); Thread.currentThread (). Spánok (oneskorenie); } catch (InterruptedException ie) {// Bolo by to prekvapenie. }}}}

Teraz tento zdroj skompilujte a spustite ako ktorúkoľvek inú aplikáciu príkazového riadku Java. Uvidíte výstup, ktorý vyzerá asi takto:

Výpis 2. Výstup z vláknového programu

Riadok # 1 z vlákna 'A' Riadok # 1 z vlákna 'C' Riadok # 1 z vlákna 'B' Riadok # 2 z vlákna 'C' Riadok # 3 z vlákna 'C' Riadok # 2 z vlákna 'B' Riadok # 4 z vlákna 'C' ... Riadok # 17 z vlákna 'B' Riadok # 14 z vlákna 'A' Riadok # 18 z vlákna 'B' Riadok # 15 z vlákna 'A' Riadok # 19 z vlákna 'B' # 16 z vlákna 'A' Riadok # 17 z vlákna 'A' Riadok # 18 z vlákna 'A' Riadok # 19 z vlákna 'A'

To je všetko - ste Java Závit programátor!

No dobre, možno nie tak rýchlo. Program v zozname 1 je rovnako malý, ale obsahuje niektoré jemnosti, ktoré si zaslúžia našu pozornosť.

Nite a neurčitosť

Typický vzdelávací cyklus s programovaním pozostáva zo štyroch etáp: (1) Štúdium nového konceptu; (2) vykonať vzorový program; (3) porovnanie výstupu s očakávaním; a (4) iterovať až do dvoch zápasov. Všimnite si však, že som predtým povedal výstup pre FirstThreadingExample bude vyzerať „niečo ako“ Výpis 2. Takže to znamená, že váš výstup by sa mohol líšiť od toho môjho, riadok po riadku. Čo je že asi?

V najjednoduchších programoch Java existuje záruka poradia vykonania: prvý riadok hlavný() sa vykonajú najskôr, potom ďalšie a tak ďalej, s príslušným sledovaním ďalších a ďalších metód. Závit oslabuje túto záruku.

Vlákanie prináša do programovania Java novú moc; výsledky môžete dosiahnuť pomocou vlákien, ktoré by ste bez nich nedokázali. Ale táto sila prichádza za cenu rozhodnosť. V najjednoduchších programoch Java existuje záruka poradia vykonania: prvý riadok hlavný() sa vykonajú najskôr, potom ďalšie a tak ďalej, s príslušným sledovaním ďalších a ďalších metód. Závit oslabuje túto záruku. Vo viacvláknovom programe „Riadok # 17 z vlákna B„sa môže zobraziť na obrazovke pred alebo po“Riadok # 14 z vlákna A„“ a poradie sa môže líšiť pri postupnom vykonávaní toho istého programu, dokonca aj v rovnakom počítači.

Neurčitosť môže byť neznáma, ale nemusí to pôsobiť rušivo. Exekučný príkaz v rámci vlákno zostáva predvídateľné a s neurčitosťou sú spojené aj výhody. Niečo podobné ste možno zažili pri práci s grafickými používateľskými rozhraniami (GUI). Príkladom sú poslucháči udalostí v aplikácii Swing alebo obslužné rutiny udalostí v HTML.

Aj keď je celá diskusia o synchronizácii vlákien mimo rozsahu tohto úvodu, je ľahké vysvetliť základné veci.

Zvážte napríklad mechaniku špecifikácie kódu HTML ... onclick = "myFunction ();" ... na určenie akcie, ktorá sa stane po kliknutí používateľa. Tento známy prípad neurčitosti ilustruje niektoré z jeho výhod. V tomto prípade, myFunction () sa nevykonáva v určitom čase vzhľadom na ostatné prvky zdrojového kódu, ale v súvislosti s činnosťou koncového používateľa. Takže neurčitosť nie je len slabosťou systému; je to tiež obohatenie modelu prevedenia, ktorý dáva programátorovi nové príležitosti na určenie postupnosti a závislosti.

Oneskorenie vykonania a podtrieda vlákna

Môžete sa z nich poučiť FirstThreadingExample tým, že s tým budete experimentovať sami. Skúste pridať alebo odstrániť ExampleThreads - teda vyvolanie konštruktora ako ... nový ExampleThread (štítok, oneskorenie); - a majstrovanie s meškanies. Základnou myšlienkou je, že program sa začína tromi samostatnými Závits, ktoré potom prebiehajú nezávisle až do dokončenia. Aby bolo ich vykonanie poučnejšie, každý z nich mierne oneskoruje medzi postupnými riadkami, ktoré zapisuje na výstup; toto dáva šancu ďalším vláknam písať ich výkon.

Poznač si to Závitprogramovanie na princípe vo všeobecnosti nevyžaduje manipuláciu s Prerušená výnimka. Ten, ktorý je uvedený v FirstThreadingExample má do činenia s spánok (), a nie priamo súvisieť s Závit. Väčšina Závitzdroj založený na zdroji nezahŕňa a spánok (); účel spánok () tu je jednoduchým spôsobom možné modelovať správanie dlhodobých metód nájdených „vo voľnej prírode“.

V zozname 1 je ešte niečo, čo si treba všimnúť Závit je abstraktné triedy, určené na zaradenie do podtriedy. Je to predvolené run () metóda nerobí nič, takže musí byť prepísaná v definícii podtriedy, aby sa dosiahlo niečo užitočné.

Toto je všetko o rýchlosti, však?

Takže teraz vidíte trochu toho, čo robí programovanie pomocou vlákien zložitým. Ale hlavný bod znášania všetkých týchto ťažkostí nie je získať rýchlosť.

Viacvláknové programy nie, spravidla sa dokončujú rýchlejšie ako jednovláknové - v skutočnosti môžu byť v patologických prípadoch podstatne pomalšie. Základná pridaná hodnota viacvláknových programov je citlivosť. Keď je JVM k dispozícii viac jadier spracovania, alebo keď program strávi značný čas čakaním na viacerých externých zdrojoch, ako sú napríklad sieťové odpovede, potom viacvláknové spracovanie môže pomôcť programu dokončiť sa rýchlejšie.

Spomeňte si na aplikáciu s grafickým používateľským rozhraním: ak stále reaguje na body koncových používateľov a kliká pri hľadaní zodpovedajúceho odtlačku prsta na pozadí alebo pri prepočítavaní kalendára pre budúcoročný tenisový turnaj, bola vytvorená s ohľadom na súbežnosť. Typická súbežná aplikačná architektúra dáva rozpoznávanie a reakciu na akcie používateľov do vlákna oddelene od výpočtového vlákna priradeného na zvládnutie veľkého koncového zaťaženia. (Ďalšie ilustrácie týchto princípov nájdete v časti „Swing threading a vlákno na odoslanie udalosti“.)

Pri vašom vlastnom programovaní potom s najväčšou pravdepodobnosťou zvážite použitie Závits za jednej z týchto okolností:

  1. Existujúca aplikácia má správnu funkčnosť, ale niekedy nereaguje. Tieto „bloky“ majú často spoločné s externými prostriedkami mimo vašej kontroly: časovo náročné databázové dotazy, komplikované výpočty, prehrávanie multimédií alebo sieťové odpovede s nekontrolovateľnou latenciou.
  2. Výpočtovo náročná aplikácia by mohla lepšie využiť viacjadrových hostiteľov. Môže to byť prípad niekoho, kto vykresľuje zložitú grafiku alebo simuluje zapojený vedecký model.
  3. Závit prirodzene vyjadruje požadovaný programovací model aplikácie. Predpokladajme napríklad, že ste modelovali správanie vodičov automobilov v dopravnej špičke alebo včiel v úli. Implementovať každého vodiča alebo včelu ako a Závitsúvisiaci objekt môže byť vhodný z hľadiska programovania, okrem akýchkoľvek úvah o rýchlosti alebo odozve.

Výzvy súbežnosti Javy

Skúsený programátor Ned Batchelder nedávno vtipkoval

Niektorí ľudia, keď sa stretnú s problémom, pomyslia si: „Viem, použijem vlákna“ a potom dvaja, ktorí majú erpoblesmy.

To je vtipné, pretože to tak dobre modeluje problém súbežnosti. Ako som už spomenul, viacvláknové programy pravdepodobne poskytnú odlišné výsledky, pokiaľ ide o presnú postupnosť alebo načasovanie vykonania vlákna. To je znepokojujúce pre programátorov, ktorí sú vyškolení na to, aby premýšľali o reprodukovateľných výsledkoch, prísnej rozhodnosti a invariantnej postupnosti.

Zhoršuje sa to. Rôzne vlákna môžu nielen produkovať výsledky v rôznych objednávkach, ale môžu aj tvrdiť na dôležitejších úrovniach výsledkov. Pre nováčika je ľahké používať viacvláknové spracovanie Zavrieť() úchyt súboru v jednom Závit pred inou Závit dokončil všetko, čo potrebuje na napísanie.

Testovanie súbežných programov

Pred desiatimi rokmi na webe JavaWorld Dave Dyer poznamenal, že jazyk Java má jednu vlastnosť tak „všadeprítomne nesprávne použitú“, že ju zaradil medzi závažné chyby v dizajne. Táto funkcia bola multithreading.

Dyerov komentár zdôrazňuje výzvu testovania viacvláknových programov. Keď už nebudete môcť ľahko určiť výstup programu z hľadiska definitívneho sledu znakov, bude to mať vplyv na to, ako efektívne môžete svoj vláknový kód otestovať.

Správny východiskový bod k vyriešeniu vnútorných ťažkostí súčasného programovania uviedol Heinz Kabutz vo svojom bulletine Java Specialist: uznajte, že súbežnosť je téma, ktorej by ste mali rozumieť, a systematicky ju študujte. K dispozícii sú samozrejme nástroje, ako sú techniky diagramov a formálne jazyky. Prvým krokom je ale zbystrenie intuície cvičením s jednoduchými programami ako FirstThreadingExample v zozname 1. Ďalej sa naučte čo najviac základných vlákien, ako sú tieto:

  • Synchronizácia a nemenné objekty
  • Naplánovanie vlákna a čakanie / upozornenie
  • Podmienky pretekov a zablokovanie
  • Monitory vlákien pre exkluzívny prístup, podmienky a tvrdenia
  • Osvedčené postupy JUnit - testovanie viacvláknového kódu

Kedy použiť Runnable

Orientácia na objekt v Jave definuje jednotlivo zdedené triedy, čo má dôsledky na kódovanie viacerých vlákien. Do tohto bodu som opísal iba použitie pre Závit ktorý bol založený na podtriedach s prepísaným run (). V dizajne objektu, ktorý už obsahoval dedičstvo, by to jednoducho nefungovalo. Nemôžete súčasne dediť z RenderedObject alebo Výrobná linka alebo MessageQueue po boku Závit!

Toto obmedzenie ovplyvňuje mnoho oblastí Java, nielen multithreading. Našťastie existuje klasické riešenie problému v podobe Spustiteľné rozhranie. Ako vysvetlil Jeff Friesen vo svojom úvode do oblasti vlákien z roku 2002, Spustiteľné rozhranie je určené pre situácie, keď je potrebná podtrieda Závit nie je možné:

The Spustiteľné rozhranie deklaruje jediný podpis metódy: void run ();. Tento podpis je totožný s Závitje run () podpis metódy a slúži ako vlákno ako záznam vykonania. Pretože Spustiteľné je rozhranie, ktorákoľvek trieda môže toto rozhranie implementovať pripojením náradie klauzula do záhlavia triedy a poskytnutím príslušnej run () metóda. V čase vykonania môže programový kód vytvoriť objekt, príp spustiteľný, z tejto triedy a odovzdať odkaz na spustiteľný súbor príslušnému Závit konštruktér.

Takže pre tie triedy, ktoré sa nemôžu rozširovať Závit, aby ste mohli využívať výhody multithreadingu, musíte vytvoriť spustiteľný súbor. Sémanticky, ak robíte programovanie na systémovej úrovni a vaša trieda je vo vzťahu is Závit, potom by ste mali podtriedu priamo z Závit. Ale väčšina použitia multithreadingu na aplikačnej úrovni závisí od zloženia, a teda definuje a Spustiteľné kompatibilný s diagramom tried aplikácie. Našťastie na kódovanie pomocou kódu stačí iba jeden alebo dva riadky navyše Spustiteľné ako je uvedené v zozname 3 nižšie.

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