Programovanie

Java 101: Pochopenie vlákien Java, Časť 3: Plánovanie vlákien a čakanie / upozorňovanie

Tento mesiac pokračujem v štvordielnom úvode do vlákien Java zameraním na plánovanie vlákien, mechanizmus čakania / upozornenia a prerušenie vlákna. Skúmate, ako buď JVM, alebo plánovač vlákien operačného systému vyberie ďalšie vlákno na vykonanie. Ako zistíte, pre výber plánovača vlákien je dôležitá priorita. Skúmate, ako vlákno čaká, kým nedostane upozornenie z iného vlákna, než bude pokračovať vo vykonávaní, a naučíte sa, ako používať mechanizmus čakania / upozornenia na koordináciu vykonávania dvoch vlákien vo vzťahu producent - spotrebiteľ. Nakoniec sa naučíte, ako predčasne prebudiť spánkové alebo čakajúce vlákno na ukončenie vlákna alebo iné úlohy. Naučím vás tiež, ako vlákno, ktoré ani nespí, ani nečaká, zistí žiadosť o prerušenie z iného 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, čakanie / upozornenie a prerušenie vlákna
  • Časť 4: Skupiny vlákien, volatilita, miestne premenné vlákna, časovače a smrť vlákna

Plánovanie vlákna

V idealizovanom svete by všetky vlákna programu mali svoje vlastné procesory, na ktorých by mohli bežať. Kým nepríde čas, keď majú počítače tisíce alebo milióny procesorov, vlákna musia často zdieľať jeden alebo viac procesorov. JVM alebo operačný systém podkladovej platformy dešifruje, ako zdieľať zdroj procesora medzi vláknami - úloha známa ako plánovanie vlákna. Tá časť JVM alebo operačného systému, ktorá vykonáva plánovanie vlákien, je plánovač vlákien.

Poznámka: Na zjednodušenie diskusie o plánovaní vlákien sa zameriavam na plánovanie vlákien v kontexte jedného procesora. Túto diskusiu môžete extrapolovať na viacerých procesorov; Túto úlohu nechám na vás.

Pamätajte na dva dôležité body týkajúce sa plánovania vlákna:

  1. Java nenúti VM, aby naplánovala vlákna konkrétnym spôsobom, ani neobsahovala plánovač vlákien. To znamená plánovanie vlákna závislé od platformy. Preto musíte byť pri písaní programu Java opatrní, ktorého správanie závisí od toho, ako sú vlákna naplánované, a musí fungovať konzistentne na rôznych platformách.
  2. Našťastie pri písaní programov Java musíte myslieť na to, ako Java naplánuje vlákna iba vtedy, keď aspoň jedno z vlákien vášho programu intenzívne využíva procesor po dlhú dobu a ako dôležité sa ukážu priebežné výsledky vykonávania tohto vlákna. Napríklad applet obsahuje vlákno, ktoré dynamicky vytvára obraz. Pravidelne chcete, aby maliarske vlákno nakreslilo aktuálny obsah tohto obrázka, aby používateľ mohol vidieť, ako obraz postupuje. Aby ste zabezpečili, že vlákno na výpočet nebude monopolizovať procesor, zvážte plánovanie vlákna.

Preskúmajte program, ktorý vytvára dve vlákna náročné na procesor:

Zoznam 1. SchedDemo.java

// SchedDemo.java trieda SchedDemo {public static void main (String [] args) {new CalcThread ("CalcThread A"). Start (); nový CalcThread ("CalcThread B"). start (); }} class CalcThread extends Thread {CalcThread (String name) {// Pass name to Thread layer. super (meno); } double calcPI () {boolean negative = true; dvojité pi = 0,0; pre (int i = 3; i <100000; i + = 2) {if (záporné) pi - = (1,0 / i); else pi + = (1,0 / i); negatívne =! negatívne; } pi + = 1,0; pi * = 4,0; návrat pi; } public void run () {for (int i = 0; i <5; i ++) System.out.println (getName () + ":" + calcPI ()); }}

SchedDemo vytvorí dve vlákna, z ktorých každá vypočíta hodnotu pí (päťkrát) a vytlačí každý výsledok. V závislosti od toho, ako vaša implementácia JVM plánuje vlákna, sa vám môže zobraziť výstup podobný tomuto:

CalcThread A: 3,1415726535897894 CalcThread B: 3,1415726535897894 CalcThread A: 3,1415726535897894 CalcThread A: 3,1415726535897894 CalcThread B: 3,1415726535897894 CalcThread A: 3,1415726535897894 CalcThread A: 3,1415726535897894 CalcThread B: 3,1415726535897894 CalcThread B: 3,1415726535897894 CalcThread B: 3,1415726535897894

Podľa vyššie uvedeného výstupu plánovač vlákien zdieľa procesor medzi oboma vláknami. Mohli ste však vidieť výstup podobný tomuto:

CalcThread A: 3,1415726535897894 CalcThread A: 3,1415726535897894 CalcThread A: 3,1415726535897894 CalcThread A: 3,1415726535897894 CalcThread A: 3,1415726535897894 CalcThread B: 3,1415726535897894 CalcThread B: 3,1415726535897894 CalcThread B: 3,1415726535897894 CalcThread B: 3,1415726535897894 CalcThread B: 3,1415726535897894

Vyššie uvedený výstup ukazuje, že plánovač vlákien uprednostňuje jedno vlákno pred druhým. Dva výstupy vyššie ilustrujú dve všeobecné kategórie plánovačov vlákien: zelený a natívny. V nasledujúcich častiach preskúmam ich rozdiely v správaní. Pri diskusii o každej kategórii sa odvolávam na stavy vlákien, z toho sú štyri:

  1. Počiatočný stav: Program vytvoril objekt vlákna vlákna, ale vlákno zatiaľ neexistuje, pretože je objekt vlákna štart () metóda ešte nebola volaná.
  2. Spustiteľný stav: Toto je predvolený stav vlákna. Po hovore na štart () dokončí, vlákno sa stane spustiteľným bez ohľadu na to, či je toto vlákno spustené, teda pomocou procesora. Aj keď je možné spustiť mnoho vlákien, momentálne je spustené iba jedno. Plánovače vlákien určujú, ktoré spustiteľné vlákno sa má priradiť k procesoru.
  3. Blokovaný stav: Keď vlákno vykoná spánok (), počkaj ()alebo pripojiť sa () metódy, keď sa vlákno pokúša načítať údaje, ktoré zo siete ešte nie sú k dispozícii, a keď vlákno čaká na získanie zámku, je toto vlákno v zablokovanom stave: nie je v prevádzke ani nie je v prevádzke. (Pravdepodobne si môžete myslieť inokedy, kedy by vlákno čakalo, kým sa niečo stane.) Keď sa blokované vlákno odblokuje, toto vlákno sa presunie do spustiteľného stavu.
  4. Stav ukončenia: Akonáhle poprava zanechá nitku run () metóda, toto vlákno je v konečnom stave. Inými slovami, vlákno prestáva existovať.

Ako si plánovač vlákien vyberie, ktoré spustiteľné vlákno sa má spustiť? Začínam odpovedať na túto otázku, keď diskutujem o plánovaní zelenej nite. Dokončujem odpoveď, zatiaľ čo diskutujem o plánovaní natívneho vlákna.

Plánovanie zelenej nite

Nie všetky operačné systémy, napríklad starodávny fungujúci systém Microsoft Windows 3.1, podporujú vlákna. Pre takéto systémy môže Sun Microsystems navrhnúť JVM, ktorý rozdeľuje jeho jediný operačný cyklus na viac vlákien. JVM (nie operačný systém základnej platformy) dodáva logiku vlákien a obsahuje plánovač vlákien. Vlákna JVM sú zelené nite, alebo používateľské vlákna.

Plánovač vlákien JVM naplánuje zelené vlákna podľa prioritou—Relatívna dôležitosť vlákna, ktorú vyjadrujete ako celé číslo z dobre definovaného rozsahu hodnôt. Plánovač vlákien JVM zvyčajne vyberá vlákno s najvyššou prioritou a umožňuje tomuto vláknu bežať, kým sa buď neukončí, alebo neblokuje. V tom čase plánovač vlákien vyberie vlákno s najvyššou prioritou. Toto vlákno (zvyčajne) beží, kým sa neukončí alebo nezablokuje. Ak sa počas činnosti vlákna odblokuje vlákno s vyššou prioritou (možno vypršal čas spánku vlákna s vyššou prioritou), plánovač vlákien preempts, alebo preruší vlákno s nižšou prioritou a priradí odblokované vlákno s vyššou prioritou procesoru.

Poznámka: Spustiteľné vlákno s najvyššou prioritou sa nebude vždy spúšťať. Tu je Špecifikácia jazyka Java 'prevziať prioritu:

Každé vlákno má a prioritou. Ak existuje konkurencia na spracovanie zdrojov, vlákna s vyššou prioritou sa zvyčajne vykonávajú pred vláknami s nižšou prioritou. Takáto preferencia však nie je zárukou, že vlákno s najvyššou prioritou bude vždy spustené, a priority vlákna nie je možné použiť na spoľahlivú implementáciu vzájomného vylúčenia.

Toto priznanie hovorí veľa o implementácii zelených vlákien JVM. Tieto JVM si nemôžu dovoliť nechať vlákna blokovať, pretože by to viazalo jediné vykonávacie vlákno JVM. Preto keď musí vlákno blokovať, napríklad keď toto vlákno číta dáta pomaly, aby dorazilo zo súboru, JVM môže zastaviť vykonávanie vlákna a na zistenie príchodu údajov použiť mechanizmus dotazovania. Zatiaľ čo vlákno zostáva zastavené, plánovač vlákien JVM môže naplánovať spustenie vlákna s nižšou prioritou. Predpokladajme, že dáta dorazia, kým je vlákno s nízkou prioritou spustené. Aj keď vlákno s vyššou prioritou by sa malo spustiť hneď po príchode údajov, nedeje sa to, kým JVM nevyzve ďalej v dotazovaní operačného systému a nezistí príchod. Preto vlákno s nižšou prioritou beží, aj keď by vlákno s vyššou prioritou malo bežať. o tejto situácie si musíte robiť starosti, iba ak potrebujete správanie Java v reálnom čase. Ale potom Java nie je operačný systém v reálnom čase, tak prečo si robiť starosti?

Aby ste pochopili, ktoré spustiteľné zelené vlákno sa stane momentálne bežiacim zeleným vláknom, zvážte nasledujúce. Predpokladajme, že sa vaša aplikácia skladá z troch vlákien: hlavného vlákna, ktoré spúšťa vlákno hlavný() metóda, výpočtové vlákno a vlákno, ktoré číta vstup z klávesnice. Ak nie je k dispozícii žiadny vstup z klávesnice, čítacie vlákno sa zablokuje. Predpokladajme, že vlákno na čítanie má najvyššiu prioritu a vlákno na výpočet má najnižšiu prioritu. (Pre zjednodušenie tiež predpokladajte, že nie sú k dispozícii žiadne ďalšie interné vlákna JVM.) Obrázok 1 zobrazuje vykonávanie týchto troch vlákien.

V čase T0 začne bežať hlavné vlákno. V čase T1 začne hlavné vlákno výpočtové vlákno. Pretože vlákno na výpočet má nižšiu prioritu ako hlavné vlákno, vlákno na výpočet čaká na procesor. V čase T2 začne hlavné vlákno čítacie vlákno. Pretože čítacie vlákno má vyššiu prioritu ako hlavné vlákno, čaká hlavné vlákno na procesor, kým sa vlákno na čítanie spustí. V čase T3 sa čítacie vlákno blokuje a beží hlavné vlákno. V čase T4 sa čítacie vlákno odblokuje a beží; hlavné vlákno čaká. Nakoniec v čase T5 blokuje čítacia niť a beží hlavné vlákno. Toto striedanie vo vykonávaní medzi čítacím a hlavným vláknom pokračuje, pokiaľ je spustený program. Vlákno na výpočet sa nikdy nespustí, pretože má najnižšiu prioritu, a preto hladuje pozornosť procesora, situácia známa ako hladovanie procesora.

Tento scenár môžeme zmeniť tak, že výpočtovému vláknu dáme rovnakú prioritu ako hlavnému vláknu. Obrázok 2 zobrazuje výsledok začínajúci časom T2. (Pred T2 je obrázok 2 totožný s obrázkom 1.)

V čase T2 beží čítacie vlákno, zatiaľ čo hlavné a výpočtové vlákna čakajú na procesor. V čase T3 sa čítacie vlákno blokuje a výpočtové vlákno sa spustí, pretože hlavné vlákno bežalo tesne pred čítacím vláknom. V čase T4 sa čítacie vlákno odblokuje a beží; hlavné a výpočtové vlákna čakajú. V čase T5 blokuje čítacie vlákno a beží hlavné vlákno, pretože výpočtové vlákno bežalo tesne pred čítacím vláknom. Toto striedanie vo vykonávaní medzi hlavným a výpočtovým vláknom pokračuje tak dlho, ako je spustený program, a závisí od spustenia a blokovania vlákna s vyššou prioritou.

Musíme zvážiť jednu poslednú položku v plánovaní zelených vlákien. Čo sa stane, keď vlákno s nižšou prioritou obsahuje zámok, ktorý vlákno s vyššou prioritou vyžaduje? Vlákno s vyššou prioritou blokuje, pretože nemôže získať zámok, čo znamená, že vlákno s vyššou prioritou má skutočne rovnakú prioritu ako vlákno s nižšou prioritou. Napríklad vlákno priority 6 sa pokúša získať zámok, ktorý vlákno priority 3 obsahuje. Pretože vlákno s prioritou 6 musí počkať, kým získa zámok, vlákno s prioritou 6 končí s prioritou 3 - jav známy ako prioritná inverzia.

Prioritná inverzia môže výrazne oddialiť vykonávanie vlákna s vyššou prioritou. Predpokladajme napríklad, že máte tri vlákna s prioritami 3, 4 a 9. Je spustené vlákno priority 3 a ostatné vlákna sú zablokované. Predpokladajme, že vlákno priority 3 sa chytí zámku a vlákno priority 4 sa odblokuje. Vlákno priority 4 sa stane vláknom, ktoré momentálne beží. Pretože vlákno s prioritou 9 vyžaduje zámok, bude čakať, kým vlákno s prioritou 3 zámok neuvoľní. Vlákno s prioritou 3 však nemôže uvoľniť zámok, kým vlákno s prioritou 4 neblokuje alebo neskončí. Vo výsledku vlákno priority 9 oneskorí jeho vykonávanie.

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