Programavimas

„Java 101“: „Java“ gijų supratimas. 3 dalis: Gijų planavimas ir laukimas / pranešimas

Šį mėnesį tęsiu keturių dalių „Java“ gijų pristatymą, sutelkdamas dėmesį į gijų planavimą, laukimo / pranešimo mechanizmą ir gijų nutraukimą. Ištirsite, kaip JVM arba operacinės sistemos gijų planavimo priemonė pasirenka kitą giją vykdymui. Kaip pastebėsite, prioritetas yra svarbus gijų planuotojo pasirinkimui. Išnagrinėsite, kaip gija laukia, kol gaus pranešimą iš kitos gijos, prieš tęsdama vykdymą, ir sužinosite, kaip naudoti laukimo / pranešimo mechanizmą derinant dviejų gijų vykdymą gamintojo ir vartotojo santykiuose. Galiausiai sužinosite, kaip per anksti pažadinti miegantį arba laukiantį siūlą siūlų nutraukimui ar kitoms užduotims atlikti. Taip pat išmokysiu, kaip gija, kuri nei miega, nei laukia, aptinka pertraukimo užklausą iš kitos gijos.

Atkreipkite dėmesį, kad šis straipsnis („JavaWorld“ archyvų dalis) buvo atnaujintas naujais kodų sąrašais ir atsisiunčiamu šaltinio kodu 2013 m. Gegužės mėn.

Suprasti „Java“ gijas - perskaitykite visą seriją

  • 1 dalis. Pristatome siūlus ir bėgimus
  • 2 dalis. Sinchronizavimas
  • 3 dalis: Gijos planavimas, laukimas / pranešimas ir gijos nutraukimas
  • 4 dalis. Gijų grupės, nepastovumas, vietiniai siūlų kintamieji, laikmačiai ir gijų mirtis

Siūlų planavimas

Idealizuotame pasaulyje visos programos gijos turėtų savo procesorius. Kol ateis laikas, kai kompiuteriuose yra tūkstančiai ar milijonai procesorių, gijos dažnai turi dalytis vienu ar keliais procesoriais. JVM arba pagrindinės platformos operacinė sistema iššifruoja, kaip dalytis procesoriaus ištekliumi tarp gijų - užduotis vadinama gijų planavimas. Ta JVM arba operacinės sistemos dalis, kuri vykdo gijų planavimą, yra a gijų planuoklis.

Pastaba: Norėdami supaprastinti diskusijas apie gijų planavimą, daugiausia dėmesio skiriu gijų planavimui vieno procesoriaus kontekste. Šią diskusiją galite ekstrapoliuoti keliems procesoriams; Palieku tą užduotį jums.

Prisiminkite du svarbius dalykus, susijusius su gijų planavimu:

  1. „Java“ neverčia VM suplanuoti gijų tam tikru būdu arba joje yra gijų planavimo priemonė. Tai reiškia, kad planavimas priklauso nuo platformos. Todėl turite būti atsargūs rašydami „Java“ programą, kurios elgesys priklauso nuo temų planavimo ir turi nuosekliai veikti skirtingose ​​platformose.
  2. Laimei, rašydami „Java“ programas, turite galvoti apie tai, kaip „Java“ suplanuoja gijas tik tada, kai bent viena iš jūsų programos gijų intensyviai naudoja procesorių ilgą laiką, o tarpiniai tos gijos vykdymo rezultatai yra svarbūs. Pavyzdžiui, programėlėje yra gija, kuri dinamiškai kuria vaizdą. Periodiškai norite, kad tapybos gija nupieštų dabartinį vaizdo turinį, kad vartotojas galėtų matyti, kaip paveikslėlis vyksta. Norėdami įsitikinti, kad skaičiavimo gija nemonopolizuoja procesoriaus, apsvarstykite siūlų planavimą.

Išnagrinėkite programą, kuri sukuria dvi daug procesoriaus reikalaujančias gijas:

Sąrašas 1. SchedDemo.java

// SchedDemo.java klasė SchedDemo {public static void main (String [] args) {new CalcThread ("CalcThread A"). Start (); nauja „CalcThread“ („CalcThread B“). pradžia (); }} klasė „CalcThread“ išplečia giją {CalcThread (String name) {// Pavadinimą perduoti siūlų sluoksniui. super (vardas); } dvigubas skaičiavimasPI () {loginis neigiamas = teisingas; dviguba pi = 0,0; už (int i = 3; i <100000; i + = 2) {jei (neigiamas) pi - = (1,0 / i); dar pi + = (1,0 / i); neigiamas =! neigiamas; } pi + = 1,0; pi * = 4,0; grįžti pi; } public void run () {for (int i = 0; i <5; i ++) System.out.println (getName () + ":" + calcPI ()); }}

„SchedDemo“ sukuria dvi gijas, kurių kiekviena apskaičiuoja pi vertę (penkis kartus) ir atspausdina kiekvieną rezultatą. Atsižvelgiant į tai, kaip jūsų JVM diegimas suplanuoja gijas, galite matyti išvestį, panašią į šią:

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

Pagal pirmiau pateiktą išvestį gijų planuotojas dalijasi procesoriumi tarp abiejų gijų. Tačiau galite pamatyti panašų išvestį:

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

Aukščiau pateiktas išvestis rodo gijų planavimo priemonę, kuri teikia pirmenybę vienai gijai, o ne kitai. Du aukščiau išvardinti rezultatai iliustruoja dvi bendras gijų planavimo priemonių kategorijas: žalią ir gimtąją. Aš ištirsiu jų elgesio skirtumus būsimose skiltyse. Aptardamas kiekvieną kategoriją, aš remiuosi gijų būsenos, iš kurių yra keturi:

  1. Pradinė būsena: Programa sukūrė gijos gijos objektą, tačiau gija dar neegzistuoja, nes gijos objektas yra pradžia () metodas dar nebuvo vadinamas.
  2. Veikiama būsena: Tai yra numatytoji gijos būsena. Po skambučio į pradžia () baigiama, gija tampa paleidžiama, nepaisant to, ar ta gija veikia, ty naudojant procesorių. Nors daugelį gijų galima paleisti, šiuo metu veikia tik viena. Gijų tvarkaraščiai nustato, kurią paleidžiamą giją priskirti procesoriui.
  3. Užblokuota būsena: Kai gija vykdo miegoti (), laukti()arba prisijungti () metodai, kai gija bando nuskaityti duomenis, kurių dar nėra iš tinklo, ir kai gija laukia, kol bus užrakinta, ši gija yra užblokuota: ji nei veikia, nei yra tokia, kad galėtų veikti. (Tikriausiai galite pagalvoti apie kitus kartus, kai gija lauktų, kol kas nors įvyks.) Kai blokuojama gija atblokuoja, ši gija pereina į paleidžiamą būseną.
  4. Baigianti būsena: Kai vykdymas palieka giją paleisti () metodas, ta gija yra baigiamojoje būsenoje. Kitaip tariant, siūlas nustoja egzistuoti.

Kaip gijų planuoklis pasirenka paleistiną giją? Aš pradedu atsakyti į šį klausimą aptardamas žaliųjų siūlų planavimą. Baigiu atsakymą aptardamas savųjų gijų planavimą.

Žaliųjų gijų planavimas

Pavyzdžiui, ne visos operacinės sistemos, senovės „Microsoft Windows 3.1“ sistema, palaiko gijas. Tokioms sistemoms „Sun Microsystems“ gali suprojektuoti JVM, kuris vienintelį jos vykdymo giją padalija į kelias gijas. JVM (ne pagrindinės platformos operacinė sistema) teikia gijų logiką ir yra gijų planavimo priemonė. JVM gijos yra žali siūlai, arba vartotojo gijos.

JVM gijų planuoklis suplanuoja žaliąsias gijas pagal prioritetas- santykinė gijos svarba, kurią išreiškiate kaip sveiką skaičių iš gerai apibrėžto verčių diapazono. Paprastai JVM gijų planuoklis pasirenka aukščiausio prioriteto giją ir leidžia šiai gijai vykdyti tol, kol ji baigsis arba blokuos. Tuo metu gijų planuotojas pasirenka kito aukšto prioriteto giją. Ta gija (paprastai) tęsiasi tol, kol ji nutrūksta arba užsiblokuoja. Jei vykdant giją aukštesnio prioriteto gija atblokuojama (galbūt pasibaigė aukštesnio prioriteto gijos miego laikas), gijų planuoklis bandymai, arba pertraukia žemesnio prioriteto giją ir priskiria atblokuotą aukštesnio prioriteto giją procesoriui.

Pastaba: Bėgama gija, turinti aukščiausią prioritetą, ne visada bus vykdoma. Štai „Java“ kalbos specifikacija “Priima prioritetą:

Kiekviena gija turi prioritetas. Kai yra konkurencija dėl išteklių apdorojimo, dažniausiai vykdomos gijos, turinčios didesnį prioritetą, o ne žemesnio prioriteto gijos. Tačiau tokia pirmenybė nėra garantija, kad visada bus vykdomas aukščiausias prioritetas, o gijų prioritetai negali būti naudojami patikimai įgyvendinant abipusę atskirtį.

Tas prisipažinimas daug pasako apie žaliųjų siūlų JVM įgyvendinimą. Tie JVM negali sau leisti leisti gijų blokuoti, nes tai surištų vienintelę JVM vykdymo giją. Todėl, kai gija turi užblokuoti, pvz., Kai ta gija lėtai duomenis skaito iš failo, JVM gali sustabdyti gijos vykdymą ir naudoti apklausos mechanizmą, kad nustatytų, kada duomenys gaunami. Kol gija lieka sustabdyta, JVM gijų planuoklis gali suplanuoti vykdyti žemesnio prioriteto giją. Tarkime, kad duomenys gaunami, kai veikia žemesnio prioriteto gija. Nors aukštesnio prioriteto gija turėtų būti paleista iškart, kai tik gaunami duomenys, tai neįvyks tol, kol JVM kitą kartą apklausia operacinę sistemą ir atranda atvykimą. Taigi, žemesnio prioriteto gija veikia, nors turėtų būti vykdoma aukštesnio prioriteto gija. Jums reikia nerimauti dėl šios situacijos tik tada, kai jums reikia „Java“ elgsenos realiuoju laiku. Bet tada „Java“ nėra realaus laiko operacinė sistema, tad kam nerimauti?

Jei norite suprasti, kuris bėgamas žalias siūlas tampa šiuo metu veikiančiu žaliu siūlu, apsvarstykite šiuos dalykus. Tarkime, kad jūsų programą sudaro trys gijos: pagrindinė gija, kuria veikia pagrindinis () metodas, skaičiavimo gija ir gija, nuskaitanti klaviatūros įvestį. Kai nėra klaviatūros įvesties, skaitymo gija blokuojama. Tarkime, kad skaitymo gija turi didžiausią prioritetą, o skaičiavimo gija - mažiausią prioritetą. (Paprastumo sumetimais taip pat tarkime, kad nėra jokių kitų vidinių JVM gijų.) 1 paveiksle pavaizduotas šių trijų gijų vykdymas.

Laiku T0 pradeda veikti pagrindinis siūlas. T1 metu pagrindinė gija pradeda skaičiavimo giją. Kadangi skaičiavimo gija turi mažesnį prioritetą nei pagrindinė gija, skaičiavimo gija laukia procesoriaus. T2 metu pagrindinė gija pradeda skaitymo giją. Kadangi skaitymo gija turi didesnį prioritetą nei pagrindinė gija, pagrindinė gija laukia procesoriaus, kol veikia skaitymo gija. T3 metu skaitymo gija blokuojasi ir veikia pagrindinė gija. T4 metu skaitymo gija atblokuoja ir veikia; pagrindinis siūlas laukia. Galiausiai, T5 metu, skaitymo gijos blokai ir pagrindinė gija eina. Šis kaitaliojimasis tarp skaitymo ir pagrindinių gijų tęsiasi tol, kol programa veikia. Skaičiavimo gija niekada nevyksta, nes ji turi mažiausią prioritetą ir todėl bado procesoriaus dėmesį, situaciją, vadinamą procesoriaus badavimas.

Šį scenarijų galime pakeisti suteikdami skaičiavimo gijai tą patį prioritetą kaip ir pagrindinė gija. 2 paveiksle parodytas rezultatas, prasidedantis laiku T2. (Iki T2 2 paveikslas yra identiškas 1 paveiksliui.)

T2 metu skaitymo gija veikia, kol pagrindinės ir skaičiavimo gijos laukia procesoriaus. T3 metu skaitymo gijos blokai ir skaičiavimo gija eina, nes pagrindinė gija vyko prieš pat skaitymo giją. T4 metu skaitymo gija atblokuoja ir veikia; pagrindinės ir skaičiavimo gijos laukia. T5 metu skaitymo gija blokuojasi ir pagrindinė gija eina, nes skaičiavimo gija vyko prieš pat skaitymo giją. Šis kaitaliojimas vykdant pagrindines ir skaičiavimo gijas tęsiasi tol, kol programa veikia ir priklauso nuo aukštesnio prioriteto gijų paleidimo ir blokavimo.

Turime atsižvelgti į paskutinį elementą, planuojant žaliąsias gijas. Kas atsitiks, kai žemesnio prioriteto gijoje yra užraktas, kurio reikalauja aukštesnio prioriteto gija? Didesnio prioriteto gijos blokuoja, nes negali gauti užrakto, o tai reiškia, kad aukštesnio prioriteto gija turi tą patį prioritetą kaip žemesnio prioriteto gija. Pavyzdžiui, 6 prioriteto gija bando įsigyti užraktą, kurį turi 3 prioriteto gija. Kadangi 6 prioriteto gija turi palaukti, kol ji galės užrakinti, 6 prioriteto gija baigiasi 3 prioritetu - reiškiniu, vadinamu prioriteto inversija.

Prioriteto inversija gali labai atitolinti aukštesnio prioriteto gijos vykdymą. Pavyzdžiui, tarkime, kad turite tris gijas, kurių prioritetai yra 3, 4 ir 9. 3 prioriteto gija veikia, o kitos gijos yra užblokuotos. Tarkime, kad 3 prioriteto gija sugriebia užraktą, o 4 prioriteto gija atblokuoja. 4 prioriteto gija tampa šiuo metu vykdoma gija. Kadangi 9 prioriteto gijai reikia užrakto, jis toliau laukia, kol 3 prioriteto gija atlaisvins užraktą. Tačiau 3 prioriteto gija negali atlaisvinti užrakto, kol 4 prioritetinė gija neužblokuoja arba nesibaigia. Todėl 9 prioriteto gija atideda jos vykdymą.