Programavimas

„JavaBeans“: ypatybės, įvykiai ir siūlų sauga

„Java“ yra dinamiška kalba, į kurią įeina lengvai naudojami daugialypiai kalbų konstrukcijos ir palaikymo klasės. Daugelis „Java“ programų remiasi daugialypiu gijimu, kad išnaudotų vidinį programų lygiagretumą, pagerintų tinklo veikimą ar paspartintų vartotojo atsiliepimus. Dauguma „Java“ vykdymo kartų naudoja daugialypį sriegį, kad įdiegtų „Java“ šiukšlių surinkimo funkciją. Pagaliau AWT taip pat remiasi atskiromis gijomis. Trumpai tariant, net paprasčiausios „Java“ programos gimsta aktyviai daugialypėje aplinkoje.

Todėl „Java“ pupelės taip pat diegiamos tokioje dinamiškoje, daugiasluoksnėje aplinkoje, ir čia slypi klasikinis pavojus susidurti lenktynių sąlygas. Lenktynių sąlygos yra nuo laiko priklausantys programų srautų scenarijai, kurie gali sukelti būsenos (programos duomenų) korupciją. Kitame skyriuje aprašysiu du tokius scenarijus. Kiekviena „Java“ pupelė turi būti suprojektuota atsižvelgiant į lenktynių sąlygas, kad pupelė galėtų atlaikyti kelių kliento siūlų naudojimą vienu metu.

Kelių sriegių problemos su paprastomis savybėmis

Pupelių diegimas turi daryti prielaidą, kad kelios gijos vienu metu pasiekia ir (arba) keičia vieną pupelių egzempliorių. Kaip netinkamai įdiegto pupelės pavyzdį (kiek tai susiję su supratimu apie daugiasriegį tekstą), apsvarstykite šią „BrokenProperties“ pupelę ir su ja susijusią MTProperties bandymo programą:

„BrokenProperties.java“

importuoti java.awt.Point;

// Demo pupelė, kuri neapsaugo nuo kelių gijų naudojimo.

viešoji klasė „BrokenProperties“ pratęsia „Point“

// ------------------------------------------------ ------------------- // set () / get () savybei „Spot“ // --------------- -------------------------------------------------- -

public void setSpot (Point point) {// 'spot' seteris this.x = point.x; tai.y = taškas.y;

} public point getSpot () {// 'spot' getter grąžink tai; }} // Pupelių pabaiga / klasė „BrokenProperties“

MTProperties.java

importuoti java.awt.Point; importo komunalinės paslaugos. *; importo komunalinės paslaugos.pupelės. *;

viešosios klasės „MTProperties“ pratęsia giją {

saugoma „BrokenProperties myBean“; // tikslinė pupelė į bash ..

saugomas int myID; // kiekviena gija turi šiek tiek ID

// ------------------------------------------------ ------------------- // pagrindinis () įvesties taškas // ---------------------- --------------------------------------------- viešasis statinis negaliojantis pagrindinis ( Stygos [] argumentai) {

„BrokenProperties“ pupelės; Sriegio sriegis;

pupelė = (BrokenProperties) BeansKit.newBean ("BrokenProperties");

for (int i = 0; i <20; i ++) {// pradėkite 20 gijų iki pupinių pupelių gijų = naujos MTP nuosavybės (pupelės, i); // gijos gauna prieigą prie pupelių gijų.start (); }} // ---------------------------------------------- --------------------- // MTProperties konstruktorius // ----------------------- --------------------------------------------

public MTProperties (BrokenProperties bean, int id) {this.myBean = pupelis; // atkreipkite dėmesį į pupelę, kad tai būtų adresuota.myID = id; // atkreipkite dėmesį, kas mes esame} // ----------------------------------------- -------------------------- // gijos pagrindinė kilpa: // darykite amžinai // sukurkite naują atsitiktinį tašką su x == y // liepk pupai priimti „Point“ kaip naują „dėmės“ savybę // paklausk pupelės, kokia jos „dėmės“ savybė dabar nustatyta // mesti klibėjimą, jei taškas x nėra lygus vietai y // --------- -------------------------------------------------- -------- public void run () {int someInt; Taškinis taškas = naujas taškas ();

while (tiesa) {someInt = (int) (Math.random () * 100); taškas.x = someInt; taškas.y = someInt; „myBean.setSpot“ (taškas);

taškas = myBean.getSpot (); if (point.x! = point.y) {System.out.println ("Pupelė sugadinta! x =" + taškas.x + ", y =" + taškas.y); System.exit (10); } System.out.print ((char) ('A' + myID)); System.out.flush (); }}} // MTP savybių klasės pabaiga

Pastaba: komunalinių paslaugų paketą importavo MTP nuosavybės yra daugkartinio naudojimo klasės ir statiniai metodai, sukurti autoriaus knygai.

Dviejuose aukščiau pateiktuose šaltinio kodų sąrašuose apibrėžta pupelė, vadinama „BrokenProperties“, ir klasė MTP nuosavybės, kuris naudojamas pupelėms pratinti iš 20 bėgančių siūlų. Leiskite mums sekti MTP nuosavybės' pagrindinis () įvesties taškas: Pirmiausia jis sukuria „BrokenProperties“ pupelę, po kurios sukuriama ir pradedama 20 gijų. Klasė MTP nuosavybės tęsiasi java.lang.Twread, todėl viskas, ką turime padaryti, kad paverstume klasę MTP nuosavybės į giją yra nepaisyti klasės Siūlas's paleisti () metodas. Mūsų gijų konstruktorius remiasi dviem argumentais: pupelių objektu, su kuriuo sriegis bendraus, ir unikaliu identifikavimu, kuris leidžia 20 gijų paleidimo metu lengvai atskirti.

Šios demonstracijos verslo pabaiga yra mūsų paleisti () metodas klasėje MTP nuosavybės. Čia mes amžinai kilpame, sukurdami atsitiktinius naujus (x, y) taškus, tačiau turėdami tokią charakteristiką: jų x koordinatė visada lygi jų y koordinatei. Šie atsitiktiniai taškai perduodami pupelėms „setSpot“ () seterio metodu ir iškart perskaitykite naudodami „getSpot“ () getter metodas. Jūs galėtumėte tikėtis perskaityti vieta savybė būti identiška atsitiktiniam taškui, sukurtam prieš kelias milisekundes. Čia yra pavyzdinis programos išvestis, kai ji iškviečiama komandinėje eilutėje:

ABBBBBBBBBBBBBBBBBBDJJJJJJJJJJJJJJJJJJJJEGHHHHHHHHHHHHHHHHHHSSSSSSSSSSSSSS IICCBBBBBBBBBBBBBBBBBKBDLJMBOPLQNRTPHHHHHHHHFFFFFFFFFFFFSSSSSSSSSSSSSSSSSS FFFFFFFFFFFFFFFAAAAAACCCCCCCKKKKKKKKKKKKKKKKKMMMMMMMMMMMMMMMMMMMMMMMMMMMDD JEOQQQQQQQQQQQQQQQRRRRRRRRRRRRRRRRRBBBBBBBBBBBBBBBTTTTTTTTTTTTTTTTLPPPPPPP PPPPGGHHHFFFFFFFFIIIIIIIIIIIIIISSSSSSSSSSSSSSSSSSSSACCCCCCCCCCCCCCCCCCCKMD QQQQQQNNNNNNNNNNNNNNNNRRRRRTRRHHHHHHHHHFFFFFFFFFFFFFFFFFFIIIIIIIIIIIIIIIII MMMJEEEEEEEEEEDDDDEEEEEEEEEOOOOOOOOOOOOOOOOOOOOOOOOOOOQNNNNNNNNBTLPLRGFFFF FFFFFFFFFIIAAAAAAAAAAAAAAAAASSSSSSSSSSSSSSSSSSKKKKKKKKKKKKKKKKCCCCCCCMMJAA AACBean sugadintas! x = 67, y = 13 OOOOOOOOOOOOOOOOOOOOO 

Išvestis rodo 20 gijų, einančių lygiagrečiai (kiek stebėtojas eina); kiekviena gija naudoja ID, kurią gavo statybos metu, kad atspausdintų vieną iš raidžių A į T, pirmosios 20 abėcėlės raidžių. Kai tik bet kuri gija atranda, kad perskaityta vieta ypatybė neatitinka užprogramuotos x = y charakteristikos, gija išspausdina pranešimą „Pupelė sugadinta“ ir sustabdo eksperimentą.

Tai, ką matote, yra pupas pažeidžiantis valstybės rasės būklės šalutinis poveikis „setSpot“ () kodas. Čia yra tas metodas dar kartą:

public void setSpot (taškinis taškas) {// 'spot' seteris this.x = point.x; tai.y = taškas.y; } 

Kas gali kada nors suklysti naudojant tokį paprastą kodą? Įsivaizduok siūlas A skambinimas „setSpot“ () taškų argumentu lygus (67,67). Jei dabar sulėtinsime Visatos laikrodį, kad galėtume matyti, kaip „Java“ virtualioji mašina (JVM) vykdo kiekvieną „Java“ sakinį po vieną, galime įsivaizduoti siūlas A x koordinatės kopijos sakinio vykdymas (tai.x = taškas.x;) ir tada staiga siūlas A užšąla operacinė sistema ir sriegis C numatoma kurį laiką veikti. Ankstesnėje veikimo būsenoje sriegis C ką tik sukūrė savo naują atsitiktinį tašką (13,13), vadinamą „setSpot“ () pati, o tada sustingo, kad atsirastų vietos siūlas M, iškart nustačius x koordinatę į 13. Taigi, atnaujinta sriegis C dabar vykdo savo užprogramuotą logiką: nustatydami y į 13 ir patikrindami, ar taškinė savybė yra lygi (13, 13), tačiau pastebi, kad ji paslaptingai pasikeitė į neteisėtą būseną (67, 13); x koordinatė yra pusė būsenos siūlas A buvo nustatymas vieta ir y koordinatė yra pusė būsenos sriegis Cbuvo nusistačiusivieta į. Galutinis rezultatas yra tas, kad „BrokenProperties“ pupelė tampa viduje nenuosekli: sugadinta nuosavybė.

Kai tik ne atominis duomenų struktūrą (tai yra struktūrą, susidedančią iš daugiau nei vienos dalies) vienu metu gali modifikuoti daugiau nei viena gija, turite apsaugoti struktūrą naudodami užraktą. „Java“ tai daroma naudojant sinchronizuotas raktinis žodis.

Įspėjimas: Skirtingai nuo visų kitų „Java“ tipų, atkreipkite dėmesį, kad „Java“ to negarantuoja ilgas ir dvigubai yra traktuojami atomiškai! Tai yra, nes ilgas ir dvigubai reikalingi 64 bitai, o tai dvigubai ilgesnis už šiuolaikinių procesoriaus architektūrų žodžių ilgį (32 bitai). Ir vieno kompiuterio žodžių įkėlimas, ir saugojimas yra iš esmės atominės operacijos, tačiau 64 bitų objektų perkėlimui reikia dviejų tokių judesių, kurių Java nesaugo dėl įprastos priežasties: našumo. (Kai kurie procesoriai leidžia užrakinti sistemos magistralę, kad būtų galima atlikti daugiažodžius perdavimus atominiu būdu, tačiau ši galimybė nėra prieinama visiems procesoriams ir bet kokiu atveju būtų nepaprastai brangu pritaikyti visiems ilgas arba dvigubai manipuliacijos!) Taigi, net kai nuosavybė susideda tik iš vieno ilgas ar viengungis dvigubai, turėtumėte naudoti visas užrakinimo atsargumo priemones, kad apsaugotumėte savo ilgesį ar dvigubą smūgį nuo staiga visiškai sugadinto.

sinchronizuotas raktinis žodis žymi kodo bloką kaip atominį žingsnį. Kodo negalima „padalyti“, nes kai kita gija pertraukia kodą, kad galėtų iš naujo įvesti tą bloką (taigi terminas grįžtantis kodas; visas „Java“ kodas turėtų būti įsijungęs). Mūsų pupelių „BrokenProperties“ sprendimas yra nereikšmingas: tiesiog pakeiskite jį „setSpot“ () metodas su šiuo:

public void setSpot (Point point) {// 'spot' setter sinchronizuotas (this) {this.x = point.x; tai.y = taškas.y; }} 

Arba kitaip:

viešas sinchronizuotas negaliojantis setSpot (taškinis taškas) {// 'spot' seteris this.x = point.x; tai.y = taškas.y; } 

Abu pakeitimai yra visiškai lygiaverčiai, nors man labiau patinka pirmas stilius, nes jis aiškiau parodo, kokia yra tiksli sinchronizuotas yra: sinchronizuotas blokas yra visada susietas su objektu, kuris užrakinamas. Iki užrakinta Aš turiu omenyje, kad JVM pirmiausia bando gauti objekto užraktą (tai yra išskirtinę prieigą) (tai yra, gauti išskirtinę prieigą prie jo), arba laukia, kol objektas bus atrakintas, jei jį užrakino kita gija. Užrakinimo procesas garantuoja, kad bet kurį objektą vienu metu gali užrakinti (arba valdyti) tik vienas siūlas.

Taigi sinchronizuotas (tai) sintaksė aiškiai atspindi vidinį mechanizmą: skliaustuose esantis argumentas yra užrakinamas objektas (dabartinis objektas) prieš įvedant kodo bloką. Alternatyvi sintaksė, kur sinchronizuotas Raktažodis yra naudojamas kaip modifikatorius metodo paraše, tai yra tiesiog trumpoji pirmojo versija.

Įspėjimas: Kai žymimi statiniai metodai sinchronizuotas, nėra tai objektas užrakinti; tik egzempliorių metodai yra susieti su dabartiniu objektu. Taigi, kai klasės metodai yra sinchronizuojami, java.lang.Klasė užraktu naudojamas metodas klasę atitinkantis objektas. Šis metodas turi rimtų rezultatų, nes klasės egzempliorių rinkinys turi vieną susietą Klasė objektas; kai tik tai Klasė objektas užrakinamas, visiems tos klasės objektams (nesvarbu, 3, 50 ar 1000!) draudžiama naudoti tą patį statinį metodą. Turint tai omenyje, turėtumėte gerai pagalvoti prieš naudodami sinchronizavimą su statiniais metodais.

Praktiškai visada prisiminkite aiškią sinchronizuotą formą, nes ji leidžia „suskaidyti“ kuo mažesnį kodo bloką metodo viduje. Stenografijos forma „atomizuoja“ visą metodą, kuris dėl našumo priežasčių dažnai būna ne ko tu nori. Kai gija įvedė atomo kodo bloką, nebereikia atlikti jokių kitų gijų bet koks sinchronizuotas to paties objekto kodas gali tai padaryti.

Patarimas: Kai objektas yra užraktas, tada visi sinchronizuotas to objekto klasės kodas taps atominiu. Todėl, jei jūsų klasėje yra daugiau nei viena duomenų struktūra, kurią reikia apdoroti atomiškai, tačiau tos duomenų struktūros yra kitaip nepriklausomas vienas kito, tada gali atsirasti dar viena veiklos kliūtis. Klientai, skambinantys sinchronizuotais metodais, kuriais manipuliuojama viena vidine duomenų struktūra, blokuos visus kitus klientus, kurie vadina kitus metodus, susijusius su bet kuriomis kitomis jūsų klasės atominių duomenų struktūromis. Aišku, turėtumėte vengti tokių situacijų, padalydami klasę į mažesnes klases, kurios vienu metu tvarko tik vieną duomenų struktūrą.

JVM įgyvendina savo sinchronizavimo funkciją sukurdamas gijų eiles, laukiančias, kol objektas bus atrakintas. Nors ši strategija yra puiki apsaugant sudėtinių duomenų struktūrų nuoseklumą, ji gali sukelti kelių gijų eismo spūstis, kai ne tokia efektyvi kodo dalis pažymima kaip sinchronizuotas.

Todėl visada atkreipkite dėmesį į tai, kiek kodo sinchronizuojate: tai turėtų būti absoliutus būtinas minimumas. Pavyzdžiui, įsivaizduokite mūsų „setSpot“ () metodą iš pradžių sudarė:

public void setSpot (Point point) {// 'spot' seteris log.println ("setSpot () paragino" + this.toString ()); tai.x = taškas.x; tai.y = taškas.y; } 

nors println pareiškimas gali logiškai priklausyti „setSpot“ () metodą, tai nėra teiginių sekos dalis, kurią reikia sugrupuoti į atominę visumą. Todėl šiuo atveju teisingas būdas naudoti sinchronizuotas raktinis žodis būtų toks:

public void setSpot (Point point) {// 'spot' seteris log.println ("setSpot () paragino" + this.toString ()); sinchronizuotas (tai) {this.x = point.x; tai.y = taškas.y; }} 

„Tinginio“ būdas ir požiūris, kurio turėtumėte vengti, atrodo taip:

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