Programavimas

„Java“ gijų programavimas realiame pasaulyje, 1 dalis

Visos „Java“ programos, išskyrus paprastas konsolines programas, yra daugialypės, nesvarbu, ar jums tai patinka, ar ne. Problema ta, kad „Abstract Windowing Toolkit“ (AWT) operacinės sistemos (OS) įvykius apdoroja savo ruožtu, todėl jūsų klausytojo metodai iš tikrųjų veikia AWT gijoje. Tie patys klausytojo metodai paprastai pasiekia objektus, kurie taip pat pasiekiami iš pagrindinės gijos. Šiuo metu gali būti viliojanti palaidoti galvą smėlyje ir apsimesti, kad jums nereikia jaudintis dėl siūlų sriegimo, tačiau paprastai to neišvengsite. Deja, praktiškai nė vienoje knygoje apie „Java“ nėra pakankamai giliai nagrinėjamos temos. (Naudingų knygų šia tema sąrašą rasite šaltiniuose.)

Šis straipsnis yra pirmasis iš serijos, kuriame bus pateikti realūs „Java“ programavimo problemų sprendimai daugialypėje aplinkoje. Tai skirta „Java“ programuotojams, kurie supranta kalbos lygio dalykus ( sinchronizuotas raktinis žodis ir įvairios Siūlas klasė), tačiau nori išmokti efektyviai naudoti šias kalbos ypatybes.

Priklausomybė nuo platformos

Deja, „Java“ pažadas apie nepriklausomybę nuo platformos netenka savo veido temų arenoje. Nors įmanoma parašyti nuo platformos nepriklausomą daugiagiją „Java“ programą, turite tai padaryti atviromis akimis. Tai iš tikrųjų nėra „Java“ kaltė; beveik neįmanoma parašyti tikrai nuo platformos nepriklausomos siūlų sistemos. (Dougo Schmidto ACE [adaptyvios komunikacijos aplinkos] pagrindas yra geras, nors ir sudėtingas bandymas. Žr. Šaltinius, kad pateiktumėte nuorodą į jo programą.) Taigi, prieš pradėdamas mokėti apie griežtus „Java“ programavimo klausimus, turiu aptarti sunkumus, kuriuos sukelia platformos, kuriose gali veikti „Java“ virtualioji mašina (JVM).

Atominė energija

Pirmoji OS lygio koncepcija, kurią svarbu suprasti, yra atomiškumas. Atomo operacijos negali nutraukti kita gija. „Java“ apibrėžia bent keletą atominių operacijų. Visų pirma, priskyrimas bet kokio tipo kintamiesiems, išskyrus ilgas arba dvigubai yra atominis. Jums nereikia jaudintis dėl to, kad gija iš anksto užkerta metodą užduoties viduryje. Praktiškai tai reiškia, kad niekada nereikia sinchronizuoti metodo, kuris nieko nedaro, bet grąžina (arba priskiria reikšmę) a loginis arba tarpt egzemplioriaus kintamasis. Panašiai nereikėtų sinchronizuoti metodo, kuris daug skaičiavo naudodamas tik vietinius kintamuosius ir argumentus ir priskyrė to skaičiavimo rezultatus egzemplioriaus kintamajam kaip paskutinį dalyką. Pavyzdžiui:

klasė some_class {int some_field; void f (some_class arg) // sąmoningai nesinchronizuotas {// Čia atlikite daug dalykų, kurie naudoja vietinius kintamuosius // ir metodo argumentus, bet nepasiekia // jokių klasės laukų (arba iškviečia bet kokius metodus //, kurie pasiekia bet kurį klasės laukai). // ... kažkoks laukas = nauja_vertė; // daryk tai paskutinis. }} 

Kita vertus, vykdant x = ++ y arba x + = y, jums gali būti suteikta teisė pasinaudoti padidinus, bet prieš paskyrimą. Norėdami gauti atomiškumą šioje situacijoje, turėsite naudoti raktinį žodį sinchronizuotas.

Visa tai svarbu, nes sinchronizavimo pridėtinės išlaidos gali būti nereikšmingos ir gali skirtis priklausomai nuo OS. Ši programa rodo problemą. Kiekviena kilpa pakartotinai vadina metodą, kuris atlieka tas pačias operacijas, bet vieną iš metodų (fiksavimas ()) sinchronizuojamas, o kitas (neužrakinti ()) nėra. Naudodama JDK „performance-pack“ VM, veikiančią naudojant „Windows NT 4“, programa praneša apie 1,2 sekundės vykdymo trukmės skirtumą tarp dviejų kilpų arba apie 1,2 mikrosekundės per skambutį. Šis skirtumas gali atrodyti nedaug, tačiau tai reiškia, kad skambučių laikas padidėjo 7,25 proc. Žinoma, procentinis padidėjimas nukrenta, nes metodas daro daugiau darbo, tačiau nemaža dalis metodų - bent jau mano programose - yra tik kelios kodo eilutės.

importuoti java.util. *; klasės sinchronizavimas {  sinchronizuotas int fiksavimas (int a, int b) {grąžinti a + b;} int neužrakinti (int a, int b) {grąžinti a + b;}  privati ​​statinė galutinė int ITERACIJOS = 1000000; statinis viešasis tuštumas main (String [] args) {synch tester = new synch (); dviguba pradžia = nauja data (). getTime ();  (ilgam i = ITERACIJOS; --i> = 0;) testeriui. blokavimas (0,0);  dviguba pabaiga = nauja data (). getTime (); dvigubo fiksavimo laikas = pabaiga - pradžia; pradžia = nauja data (). getTime ();  už (ilgas i = ITERACIJOS; --i> = 0;) testeris. neužrakina (0,0);  pabaiga = nauja data (). getTime (); dvigubas neužrakinimo laikas = pabaiga - pradžia; dvigubas laikas: sinchronizavimas = užrakinimo laikas - neužrakinimo laikas; System.out.println ("Sinchronizavimui prarastas laikas (milis.):" + Time_in_synchronization); System.out.println ("Užrakinti pridėtines išlaidas vienam skambučiui:" + (laiko_in_ sinchronizavimas / ITERACIJOS)); System.out.println (ne_blokavimo_ laikas / fiksavimo laikas * 100,0 + "% padidėjimas"); }} 

Nors „HotSpot“ virtualioji mašina turėtų spręsti sinchronizavimo ir pridėtinės problemos problemą, „HotSpot“ nėra nemokama priemonė - jūs turite ją nusipirkti. Nebent jūs licencijuojate ir nesiunčiate „HotSpot“ kartu su savo programa, negalima pasakyti, koks VM bus tikslinėje platformoje, ir, žinoma, norite, kad kuo mažiau programos vykdymo greičio būtų priklausomas nuo ją vykdančios VM. Net jei aklavietės problemų (kurias aptarsiu kitoje šios serijos dalyje) neegzistavo, mintis, kad turėtumėte „viską sinchronizuoti“, yra tiesiog neteisinga.

Lygiagretumas ir lygiagretumas

Kita su OS susijusi problema (ir pagrindinė problema, kai reikia rašyti nuo platformos nepriklausomą „Java“) yra susijusi su sąvokomis sutapimas ir lygiagretumas. Vienu metu veikiančios kelių gijų sistemos suteikia kelias vienu metu vykdomas užduotis, tačiau šios užduotys iš tikrųjų yra padalintos į dalis, kurios dalijasi procesoriumi su kitų užduočių dalimis. Šis paveikslėlis iliustruoja problemas. Lygiagrečiose sistemose iš tikrųjų vienu metu atliekamos dvi užduotys. Lygiagretumui reikalinga kelių procesorių sistema.

Jei nepraleidžiate daug laiko užblokuotai, laukdami, kol baigsis įvesties / išvesties operacijos, programa, naudojanti keletą vienu metu esančių gijų, dažnai veiks lėčiau nei lygiavertė vienos gijos programa, nors ji dažnai bus geriau organizuota nei lygiavertė viena -gijos versija. Programa, kuri naudoja kelias gijas, vienu metu veikiančias keliuose procesoriuose, veiks daug greičiau.

Nors „Java“ leidžia visiškai įdiegti VM, bent jau teoriškai, šis metodas pašalins jūsų programos paralelumą. Jei operacinės sistemos lygio gijos nebūtų naudojamos, OS pažvelgtų į VM egzempliorių kaip į vienos gijos programą, kuri greičiausiai būtų suplanuota vienam procesoriui. Grynas rezultatas būtų tas, kad jokios dvi „Java“ gijos, veikiančios pagal tą patį VM egzempliorių, niekada nevyktų lygiagrečiai, net jei turėtumėte kelis procesorius ir jūsų VM būtų vienintelis aktyvus procesas. Du VM, veikiančių atskiromis programomis, atvejai, žinoma, galėtų veikti lygiagrečiai, bet aš noriu tai padaryti geriau. Norėdami gauti lygiagretumą, VM turi susieti „Java“ gijas su OS gijomis; Taigi, jūs negalite sau leisti ignoruoti skirtingų sriegių modelių skirtumų, jei platformos nepriklausomybė yra svarbi.

Išsiaiškinkite savo prioritetus

Parodysiu, kaip ką tik aptartos problemos gali paveikti jūsų programas palygindamos dvi operacines sistemas: „Solaris“ ir „Windows NT“.

„Java“ bent jau teoriškai siūlo dešimt prioritetinių gijų lygių. (Jei du ar daugiau gijų laukia paleidimo, bus vykdoma ta, kurios prioritetas yra aukščiausias.) „Solaris“, palaikančiame 231 prioriteto lygmenį, tai nėra problema (nors „Solaris“ prioritetus naudoti gali būti keblu - daugiau apie tai per akimirką). Kita vertus, NT turi septynis prioritetinius lygius, kuriuos reikia susieti su „Java“ dešimčia. Šis žemėlapis nėra apibrėžtas, todėl pateikiama daugybė galimybių. (Pavyzdžiui, „Java“ 1 ir 2 prioritetų lygiai gali priskirti NT 1 prioriteto lygmenį, o „Java“ 8, 9 ir 10 prioriteto lygiai gali priskirti NT 7 lygį.)

NT prioritetinių lygių trūkumas yra problema, jei norite naudoti prioritetą planavimui valdyti. Viską dar labiau apsunkina tai, kad prioritetų lygiai nėra nustatyti. NT pateikia mechanizmą, vadinamą prioriteto didinimas, kurį galite išjungti naudodami C sistemos skambutį, bet ne iš „Java“. Kai įjungtas prioriteto didinimas, NT padidina gijos prioritetą neapibrėžta suma neribotam laikui kiekvieną kartą, kai ji vykdo tam tikrus su I / O susijusius sistemos skambučius. Praktiškai tai reiškia, kad gijos prioriteto lygis gali būti aukštesnis, nei jūs manote, nes ta gija įvyko įvesties / išvesties operaciją nepatogiu metu.

Pirmenybės didinimo tikslas yra užkirsti kelią temoms, kurios apdoroja foną, paveikti akivaizdų UI sunkių užduočių reagavimą. Kitos operacinės sistemos turi sudėtingesnius algoritmus, kurie paprastai sumažina foninių procesų prioritetą. Šios schemos trūkumas, ypač įgyvendinant kiekvieno gijos, o ne proceso lygmenį, yra tai, kad labai sunku naudoti prioritetą norint nustatyti, kada tam tikra gija bus paleista.

Blogėja.

„Solaris“, kaip ir visose „Unix“ sistemose, procesai turi prioritetą ir gijas. Aukšto prioriteto procesų gijų negalima pertraukti žemo prioriteto procesų gijomis. Be to, tam tikro proceso prioriteto lygį gali apriboti sistemos administratorius, kad vartotojo procesas nenutrauktų kritinių OS procesų. NT nieko iš to nepalaiko. NT procesas yra tik adreso erdvė. Ji neturi prioriteto per se ir nėra suplanuota. Sistema suplanuoja gijas; tada, jei tam tikra gija vykdoma procese, kurio nėra atmintyje, procesas yra pakeičiamas. NT gijos prioritetai skirstomi į įvairias „prioritetų klases“, kurios yra paskirstytos ištisų faktinių prioritetų. Sistema atrodo taip:

Stulpeliai yra tikri prioriteto lygiai, iš kurių tik 22 turi būti bendrinamos visose programose. (Kitus naudoja pats NT.) Eilės yra prioritetinės klasės. Siūlai, vykstantys procese, susietame su tuščiosios eigos prioriteto klase, veikia 1–6 ir 15 lygiuose, atsižvelgiant į jiems priskirtą loginį prioriteto lygį. Proceso, susieto kaip įprasto prioriteto klasės, gijos veiks 1, 6–10 arba 15 lygiu, jei procesas neturi įvesties dėmesio. Jei jis turi įvesties židinį, gijos veikia nuo 1, 7 iki 11 arba 15 lygių. Tai reiškia, kad nenaudojamo prioriteto klasės proceso aukšto prioriteto gija gali užkirsti kelią mažo prioriteto įprastos prioritetinės klasės procesui, bet tik tuo atveju, jei tas procesas vyksta fone. Atkreipkite dėmesį, kad „aukšto“ prioriteto klasėje vykstančiame procese prieinami tik šeši prioriteto lygiai. Kitose klasėse yra septyni.

NT nėra būdas apriboti prioritetinę proceso klasę. Bet kuri bet kurio mašinos proceso gija gali bet kada perimti dėžės valdymą, padidindama savo prioritetinę klasę; nuo to nėra gynybos.

Techninis terminas, kurį vartoju apibūdindamas NT prioritetą, yra nešventa netvarka. Praktiškai, taikant NT, prioritetas yra beveik bevertis.

Taigi, ką daryti programuotojui? Tarp riboto NT prioritetinių lygių skaičiaus ir nekontroliuojamo prioritetų didinimo nėra visiškai saugaus būdo „Java“ programai naudoti prioritetinius lygius planuojant. Vienas veiksmingas kompromisas yra apsiriboti Siūlas.MAX_PRIORITY, Siūlas.MIN_PRIORITYir Siūlai.NORM_PRIORITY kai skambini setPriority (). Šiuo apribojimu bent jau išvengiama 10 lygių susietų su 7 lygiais problemos. Manau, kad galėtumėte naudoti os.pavadinimas sistemos ypatybė aptikti NT ir paskui iškvieskite savąjį metodą, kad išjungtumėte prioritetų didinimą, tačiau tai neveiks, jei jūsų programa veikia naudojant „Internet Explorer“, nebent naudosite „Sun“ VM papildinį. („Microsoft“ VM naudoja nestandartinį vietinio metodo įgyvendinimą.) Bet kokiu atveju nekenčiu naudoti savųjų metodų. Paprastai aš kiek įmanoma labiau išvengiu problemos, įdėdamas daugumą gijų NORM_PRIORITY ir naudoti planavimo mechanizmus, išskyrus prioritetą. (Kai kuriuos iš jų aptarsiu būsimose šios serijos dalyse.)

Bendradarbiaukite!

Paprastai yra du siūlų modeliai, kuriuos palaiko operacinės sistemos: kooperatyvus ir prevencinis.

Kooperatyvinis daugiagijis modelis

A kooperatyvas sistema, gija išlaiko savo procesoriaus kontrolę, kol nusprendžia jo atsisakyti (ko gali būti niekada). Įvairios gijos turi bendradarbiauti tarpusavyje arba visos gijos, išskyrus vieną, bus „išalkusios“ (tai reiškia, kad niekada nebuvo suteikta galimybė paleisti). Planavimas daugumoje kooperatyvinių sistemų atliekamas griežtai pagal prioritetinį lygį. Kai dabartinė gija atsisako valdymo, aukščiausio prioriteto laukimo gija gauna valdymą. (Šios taisyklės išimtis yra „Windows 3.x“, kuri naudoja kooperatyvo modelį, bet neturi daug planuoklio. Langas, kuriame yra dėmesys, tampa valdomas.)