Programovanie

Java 101: Pochopenie vlákien Java, 1. časť: Predstavujeme vlákna a spustiteľné súbory

Tento článok je prvý zo štyroch častí Java 101 séria skúmajúca vlákna Java. Aj keď si môžete myslieť, že vlákna v Jave by boli náročné na uchopenie, mám v úmysle ukázať vám, že vlákna sú ľahko pochopiteľné. V tomto článku vám predstavím vlákna Java a spustiteľné súbory. V nasledujúcich článkoch preskúmame synchronizáciu (prostredníctvom zámkov), problémy so synchronizáciou (napríklad zablokovanie), mechanizmus čakania / upozornenia, plánovanie (s prioritou alebo bez nej), prerušenie vlákna, časovače, volatilita, skupiny vlákien a miestne premenné vlákna .

Upozorňujeme, že tento článok (súčasť archívov JavaWorld) bol v máji 2013 aktualizovaný o nové zoznamy kódov a zdrojový kód na stiahnutie.

Pochopenie vlákien Java - prečítajte si celú sériu

  • Časť 1: Predstavujeme vlákna a spustiteľné súbory
  • Časť 2: Synchronizácia
  • Časť 3: Plánovanie vlákna a čakanie / upozornenie
  • Časť 4: Skupiny vlákien a volatilita

Čo je to vlákno?

Koncepčne je pojem a závit nie je ťažké ho pochopiť: je to nezávislá cesta vykonania prostredníctvom programového kódu. Keď sa vykoná viac vlákien, cesta jedného vlákna rovnakým kódom sa zvyčajne líši od ostatných. Predpokladajme napríklad, že jedno vlákno vykoná ekvivalent bajtového kódu príkazu if-else ak časti, zatiaľ čo iné vlákno vykonáva ekvivalent bajtového kódu k inak časť. Ako JVM sleduje vykonávanie každého vlákna? JVM dáva každému vláknu svoj vlastný zásobník volaní metód. Okrem sledovania aktuálnej inštrukcie bajtového kódu sleduje zásobník volání metódy lokálne premenné, parametre, ktoré JVM odovzdáva metóde, a návratovú hodnotu metódy.

Keď viaceré vlákna vykonávajú sekvencie inštrukcií bajtového kódu v rovnakom programe, táto akcia je známa ako multithreading. Viacvláknové spracovanie programu prináša výhody rôznymi spôsobmi:

  • Programy založené na viacvláknovom grafickom používateľskom rozhraní (grafické používateľské rozhranie) zostávajú pohotové voči používateľom aj pri vykonávaní ďalších úloh, ako je napríklad zmena veľkosti alebo tlač dokumentu.
  • Programy so závitom zvyčajne končia rýchlejšie ako ich kolegovia bez vlákna. To platí najmä pre vlákna bežiace na viacprocesorovom stroji, kde každé vlákno má svoj vlastný procesor.

Java dosahuje multithreading prostredníctvom svojich java.lang.Thread trieda. Každý Závit objekt popisuje jedno vlákno vykonania. Táto poprava sa koná v Závitje run () metóda. Pretože predvolené run () metóda nerobí nič, musíte podtriedu Závit a prepísať run () vykonať užitočnú prácu. Pre chuť nití a viacvláknové spracovanie v kontexte Závitpreskúmať výpis 1:

Zoznam 1. ThreadDemo.java

// ThreadDemo.java class ThreadDemo {public static void main (String [] args) {MyThread mt = new MyThread (); mt.start (); pre (int i = 0; i <50; i ++) System.out.println ("i =" + i + ", i * i =" + i * i); }} class MyThread extends Thread {public void run () {for (int count = 1, row = 1; row <20; row ++, count ++) {for (int i = 0; i <count; i ++) System.out. print ('*'); System.out.print ('\ n'); }}}

Výpis 1 predstavuje zdrojový kód aplikácie pozostávajúcej z tried ThreadDemo a MyThread. Trieda ThreadDemo riadi aplikáciu vytvorením a MyThread objekt, spustenie vlákna, ktoré je s týmto objektom spojené, a vykonanie nejakého kódu na vytlačenie tabuľky štvorcov. Naproti tomu MyThread prepíše Závitje run () metóda tlače (na štandardnom výstupnom toku) pravouhlého trojuholníka zloženého z hviezdičiek.

Plánovanie vlákna a JVM

Väčšina (ak nie všetky) implementácií JVM využíva možnosti vlákien podkladovej platformy. Pretože tieto funkcie sú špecifické pre jednotlivé platformy, poradie výstupu vašich viacvláknových programov sa môže líšiť od poradia výstupu niekoho iného. Tento rozdiel vyplýva z plánovania, témy, ktorej sa venujem neskôr v tejto sérii.

Keď píšete java ThreadDemo na spustenie aplikácie vytvorí JVM štartovacie vlákno vykonania, ktoré vykoná hlavný() metóda. Vykonaním mt.start ();, počiatočné vlákno povie JVM, aby vytvorilo druhé vykonávacie vlákno, ktoré vykonáva inštrukcie bajtového kódu obsahujúce MyThread objektu run () metóda. Keď štart () metóda sa vráti, počiatočné vlákno vykoná svoje pre slučky, ak chcete vytlačiť tabuľku štvorcov, zatiaľ čo nové vlákno vykoná znak run () spôsob tlače pravouhlého trojuholníka.

Ako vyzerá výstup? Bež ThreadDemo zistiť. Všimnete si, že výstup každého vlákna má tendenciu sa prelínať s výstupom druhého vlákna. Výsledkom je, že obe vlákna odosielajú svoj výstup do rovnakého štandardného výstupného toku.

Trieda vlákna

Aby ste sa zdokonalili v písaní viacvláknového kódu, musíte najskôr porozumieť rôznym metódam, ktoré tvoria Závit trieda. Táto časť skúma mnohé z týchto metód. Konkrétne sa dozviete o metódach na spustenie vlákien, pomenovanie vlákien, uvedenie vlákien do režimu spánku, určenie, či je vlákno živé, ako sa spojiť jedno vlákno s iným vláknom a ako sa majú vymenovať všetky aktívne vlákna v skupine vlákien a podskupinách aktuálneho vlákna. Aj diskutujem Závitladiace pomôcky a vlákna používateľov oproti vláknam démona.

Zvyšok predstavím Závitmetódy v nasledujúcich článkoch, s výnimkou metód, ktoré spoločnosť Sun zastarala.

Zastarané metódy

Spoločnosť Sun už zastarala rôzne Závit metódy, ako napr pozastaviť () a pokračovať(), pretože môžu uzamknúť vaše programy alebo poškodiť objekty. Vo výsledku by ste ich nemali volať vo svojom kóde. Riešenie týchto metód nájdete v dokumentácii SDK. Nezahŕňam zastarané metódy v tejto sérii.

Konštruovanie vlákien

Závit má osem konštruktérov. Najjednoduchšie sú:

  • Vlákno (), čím sa vytvorí a Závit objekt s predvoleným názvom
  • Vlákno (názov reťazca), ktorá vytvára a Závit objekt s názvom, ktorý názov argument určuje

Ďalším najjednoduchším konštruktorom sú Vlákno (spustiteľný cieľ) a Vlákno (spustiteľný cieľ, názov reťazca). Okrem Spustiteľné parametre sú tieto konštruktory identické s vyššie uvedenými konštruktormi. Rozdiel: Spustiteľné parametre identifikujú objekty vonku Závit ktoré poskytujú run () metódy. (Dozviete sa o Spustiteľné ďalej v tomto článku.) Konečné štyri konštruktory sa podobajú Vlákno (názov reťazca), Vlákno (spustiteľný cieľ)a Vlákno (spustiteľný cieľ, názov reťazca); finálne konštruktory však zahŕňajú aj a ThreadGroup argument na organizačné účely.

Jeden z posledných štyroch konštruktérov, Vlákno (skupina ThreadGroup, spustiteľný cieľ, názov reťazca, dlhá veľkosť zásobníka), je zaujímavé tým, že vám umožňuje určiť požadovanú veľkosť zásobníka vlákien pre volanie metód. Schopnosť určiť túto veľkosť sa v programoch s metódami, ktoré využívajú rekurziu - techniku ​​vykonávania, pri ktorej sa metóda opakovane volá - osvedčuje pri elegantnom riešení určitých problémov. Explicitným nastavením veľkosti stohu môžete niekedy zabrániť StackOverflowErrors. Výsledkom však môže byť príliš veľká veľkosť OutOfMemoryErrors. Spoločnosť Sun tiež považuje veľkosť zásobníka metodických hovorov za závislú od platformy. V závislosti na platforme sa veľkosť zásobníka hovorov metódou môže meniť. Pred napísaním kódu, ktorý volá, si preto dobre premyslite dôsledky, ktoré má váš program Vlákno (skupina ThreadGroup, spustiteľný cieľ, názov reťazca, dlhá veľkosť zásobníka).

Naštartujte svoje vozidlá

Vlákna pripomínajú vozidlá: presúvajú programy od začiatku do konca. Závit a Závit objekty podtriedy nie sú vlákna. Namiesto toho popisujú atribúty vlákna, napríklad jeho názov, a obsahujú kód (pomocou a. Vlákna) run () metóda), ktorú vlákno vykoná. Až príde čas na vykonanie nového vlákna run (), ďalšie vlákno volá Závitalebo jeho podtrieda štart () metóda. Napríklad na spustenie druhého vlákna, počiatočného vlákna aplikácie, ktoré sa vykoná hlavný()—Volá štart (). V reakcii na to kód manipulácie s vláknami JVM pracuje s platformou, aby zabezpečil správne inicializáciu vlákna a volá a Závitalebo jeho podtrieda run () metóda.

Raz štart () dokončí, vykoná sa viac vlákien. Pretože máme tendenciu myslieť lineárne, často je pre nás ťažké pochopiť súbežne (simultánna) aktivita, ktorá nastane, keď sú spustené dve alebo viac vlákien. Preto by ste mali preskúmať graf, ktorý ukazuje, kde sa vlákno vykonáva (jeho pozícia) v porovnaní s časom. Obrázok nižšie predstavuje takýto graf.

Graf zobrazuje niekoľko významných časových období:

  • Inicializácia počiatočného vlákna
  • V okamihu, keď sa toto vlákno začne vykonávať hlavný()
  • V okamihu, keď sa toto vlákno začne vykonávať štart ()
  • Moment štart () vytvorí nové vlákno a vráti sa do hlavný()
  • Inicializácia nového vlákna
  • V okamihu, keď sa nové vlákno začne vykonávať run ()
  • Rôzne momenty, ktoré každé vlákno končí

Upozorňujeme, že inicializácia nového vlákna, jeho vykonávanie run ()a jeho ukončenie nastane súčasne s vykonaním počiatočného vlákna. Upozorňujeme tiež, že po vyvolaní vlákna štart (), následné volania tejto metódy pred run () metóda končí príčina štart () hodiť a java.lang.IllegalThreadStateException objekt.

Čo je v názve?

Počas relácie ladenia sa ukáže užitočné odlíšiť jedno vlákno od druhého užívateľsky príjemným spôsobom. Na rozlíšenie medzi vláknami Java spája názov s vláknom. Tento názov je predvolený Závit, spojovník a celé číslo od nuly. Môžete prijať predvolené názvy vlákien Java alebo si môžete zvoliť vlastné. Pre prispôsobenie vlastných mien, Závit poskytuje konštruktérov, ktorí berú názov argumenty a a setName (názov reťazca) metóda. Závit tiež poskytuje a getName () metóda, ktorá vráti aktuálny názov. Zoznam 2 ukazuje, ako vytvoriť vlastné meno pomocou Vlákno (názov reťazca) konštruktor a vyhľadajte aktuálny názov v priečinku run () metóda volaním getName ():

Zoznam 2. NameThatThread.java

// NameThatThread.java trieda NameThatThread {public static void main (String [] args) {MyThread mt; if (args.length == 0) mt = new MyThread (); else mt = new MyThread (args [0]); mt.start (); }} class MyThread extends Thread {MyThread () {// Kompilátor vytvorí ekvivalent bajtového kódu super (); } MyThread (Názov reťazca) {super (meno); // Zadajte meno do nadtriedy vlákna} public void run () {System.out.println ("Moje meno je:" + getName ()); }}

Môžete vložiť voliteľný argument názvu do MyThread na príkazovom riadku. Napríklad, java NameThatThread X ustanovuje X ako názov vlákna. Ak nezadáte názov, zobrazí sa nasledujúci výstup:

Moje meno je: Thread-1

Ak chcete, môžete zmeniť super (meno); zavolajte na MyThread (názov reťazca) konštruktor na volanie setName (názov reťazca)-ako v setName (meno);. Toto posledné volanie metódy dosahuje rovnaký cieľ - stanovenie názvu vlákna - ako super (meno);. Nechávam to ako cvičenie pre vás.

Pomenovanie hlavné

Java priradí názov hlavný na vlákno, ktoré spúšťa hlavný() metóda, počiatočné vlákno. Toto meno sa zvyčajne zobrazuje v Výnimka vo vlákne „hlavný“ správa, ktorú predvolený obslužný program výnimiek JVM vytlačí, keď začiatočné vlákno hodí objekt výnimky.

Spať alebo nespať

Ďalej v tomto stĺpci vám predstavím animácia- opakované kreslenie na jeden povrch obrazov, ktoré sa od seba mierne líšia, aby sa dosiahla pohybová ilúzia. Na dokončenie animácie musí byť vlákno pozastavené počas zobrazovania dvoch po sebe nasledujúcich obrázkov. Telefonovanie Závitje statický spánok (dlhé roky) metóda vynúti pozastavenie vlákna millis milisekundy. Spacie vlákno by mohlo prerušiť iné vlákno. Ak sa to stane, spánková niť sa prebudí a hodí Prerušená výnimka objekt z spánok (dlhé roky) metóda. Výsledkom je kód, ktorý volá spánok (dlhé roky) sa musí objaviť v rámci a skús blok - alebo metóda kódu musí obsahovať Prerušená výnimka v jeho hodí doložka.

Demonštrovať spánok (dlhé roky), Napísal som a CalcPI1 žiadosť. Táto aplikácia spustí nové vlákno, ktoré používa matematický algoritmus na výpočet hodnoty matematickej konštanty pi. Zatiaľ čo sa nové vlákno počíta, počiatočné vlákno sa pozastaví na 10 milisekúnd volaním spánok (dlhé roky). Po prebudení počiatočného vlákna vytlačí hodnotu pi, ktorú nové vlákno ukladá do premennej pi. Zoznam 3 darčekov CalcPI1zdrojový kód:

Zoznam 3. CalcPI1.java

// CalcPI1.java class CalcPI1 {public static void main (String [] args) {MyThread mt = new MyThread (); mt.start (); skus {Thread.sleep (10); // Spánok na 10 milisekúnd} catch (InterruptedException e) {} System.out.println ("pi =" + mt.pi); }} class MyThread extends Thread {boolean positive = true; dvojité pi; // Inicializuje sa na 0,0, predvolene public void run () {for (int i = 3; i <100000; i + = 2) {if (negative) pi - = (1,0 / i); else pi + = (1,0 / i); negatívne =! negatívne; } pi + = 1,0; pi * = 4,0; System.out.println ("Dokončený výpočet PI"); }}

Ak spustíte tento program, uvidíte výstup podobný (ale pravdepodobne nie identický) nasledujúcemu:

pi = -0,2146197014017295 Hotový výpočet PI
$config[zx-auto] not found$config[zx-overlay] not found