Programavimas

Naudojant gijas su kolekcijomis, 1 dalis

Gijos yra neatsiejama „Java“ kalbos dalis. Naudojant gijas, daugeliui algoritmų, pavyzdžiui, eilių valdymo sistemoms, lengviau pasiekti, nei naudojant apklausos ir ciklo metodus. Neseniai, rašydamas „Java“ klasę, supratau, kad man reikia naudoti gijas, skaičiuojant sąrašus, ir tai atskleidė keletą įdomių problemų, susijusių su gijas žinančiomis kolekcijomis.

Tai „Java“ gylis stulpelyje aprašomos problemos, kurias aptikau bandydamas sukurti saugų siūlų rinkinį. Kolekcija vadinama „saugia siūlais“, kai ją vienu metu gali saugiai naudoti keli klientai (siūlai). - Taigi kokia problema? Jūs klausiate. Problema ta, kad įprastu būdu programa pakeičia kolekciją (vadinamą mutuodamas), ir ją perskaito (vadinamas išvardijant).

Kai kurie žmonės tiesiog neregistruoja teiginio: „Java platforma yra daugialypė“. Aišku, jie tai girdi ir linkteli galva. Bet jie nesupranta, kad, skirtingai nei C arba C ++, kai sriegimas buvo priveržtas iš šono per OS, „Java“ siūlai yra pagrindiniai kalbos komponentai. Šis nesusipratimas arba prastas suvokimas dėl savaime įsisukusio „Java“ pobūdžio neišvengiamai sukelia du įprastus programuotojų „Java“ kodo trūkumus: arba jie nesugeba deklaruoti metodo kaip sinchronizuoto, kuris turėtų būti (nes objektas yra nenuoseklios būsenos metu). metodo vykdymą) arba jie paskelbia, kad metodas yra sinchronizuotas, kad jį apsaugotų, dėl ko likusi sistemos dalis veikia neefektyviai.

Aš susidūriau su šia problema, kai norėjau kolekcijos, kurią galėtų naudoti kelios gijos, be reikalo blokuodamos kitų gijų vykdymą. Nė viena iš 1.1 versijos JDK kolekcijos klasių nėra saugi. Nė viena kolekcijos klasė neleis jums išvardyti vienos gijos, o mutuoti su kita.

Kolekcijos, kuriose nėra siūlų

Mano pagrindinė problema buvo tokia: darant prielaidą, kad turite užsakytą objektų kolekciją, suprojektuokite „Java“ klasę taip, kad gija galėtų išvardyti visą ar dalį kolekcijos, nesijaudindama, kad išvardijimas negalios dėl kitų gijų, kurios keičia kolekciją. Kaip problemos pavyzdį galime paminėti „Java“ Vektorius klasė. Ši klasė nėra saugi siūlams ir kelia daug problemų naujiems „Java“ programuotojams, kai jie ją sujungia su daugiasriegiu programa.

Vektorius klasė suteikia labai naudingą galimybę Java programuotojams: būtent dinamiško dydžio objektų masyvas. Praktiškai galite naudoti šią galimybę rezultatams saugoti, kai galutinis objektų, su kuriais susidursite, skaičius nėra žinomas, kol nepadarysite jų visų. Sukūriau šį pavyzdį, kad pademonstruočiau šią koncepciją.

01 importuoti java.util.Vector; 02 importuoti java.util.Skaitymas; 03 public class Demo {04 public static void main (String args []) {05 Vektoriaus skaitmenys = naujas vektorius (); 06 vid rezultatas = 0; 07 08 if (args.length == 0) {09 System.out.println ("Naudojimas yra java demo 12345"); 10 „System.exit“ (1); 11} 12 13 (int i = 0; i = '0') && (c <= '9')) 16 skaitmenų.addElement (naujas sveikasis skaičius (c - '0')); 17 dar 18 pertraukos; 19} 20 System.out.println ("Yra" + skaitmenų.dydis () + "skaitmenų"); 21 for (Surašymas e = skaitmenys.elementai (); e.hasMoreElements ();) {22 rezultatas = rezultatas * 10 + ((Sveikasis skaičius) e.nextElement ()). IntValue (); 23} 24 System.out.println (argumentai [0] + "=" + rezultatas); 25 „System.exit“ (0); 26} 27} 

Aukščiau pateiktoje paprastoje klasėje naudojamas a Vektorius objektas surinkti skaitmenų simbolius iš eilutės. Tada surenkama eilutė, kad būtų apskaičiuota sveika skaičiaus eilutė. Šiai klasei nėra nieko blogo, išskyrus tai, kad ji nėra saugi siūlams. Jei kitoje temoje atsitiktų nuoroda į skaitmenų vektoriaus, o tas siūlas įterpė naują simbolį į vektorių, kilpos, esančios 21–23 eilutėse, rezultatai būtų nenuspėjami. Jei įterpimas įvyko anksčiau nei išvardinimo objektas praėjo įterpimo tašką, gija skaičiuojama rezultatas apdorotų naują personažą. Jei įterpimas įvyko po to, kai surašymas praėjo įterpimo tašką, kilpa neapdorojo simbolio. Blogiausias scenarijus yra tas, kad kilpa gali mesti a „NoSuchElementException“ jei būtų pažeistas vidinis sąrašas.

Šis pavyzdys yra būtent toks - sugalvotas pavyzdys. Tai parodo problemą, bet kokia tikimybė, kad kita gija bus paleista per trumpą penkių ar šešių skaitmenų surašymą? Šiame pavyzdyje rizika yra maža. Laikas, praeinantis, kai viena gija pradeda rizikingą operaciją, kuri šiame pavyzdyje yra išvardijimas, o tada užduotis baigia, vadinama gijos pažeidžiamumo langasarba langas. Šis konkretus langas yra žinomas kaip lenktynių būklė nes viena gija „lenktyniauja“, kad užbaigtų savo užduotį, kol kita gija naudoja kritinį šaltinį (skaitmenų sąrašą). Tačiau kai pradedate naudoti kolekcijas kelių tūkstančių elementų grupei reprezentuoti, pvz., Su duomenų baze, pažeidžiamumo langas padidėja, nes gijų išvardijimas praleis daug daugiau laiko savo surašymo cikle ir tai suteiks galimybę paleisti kitą giją daug aukščiau. Jūs tikrai nenorite, kad kita tema pakeistų sąrašą po savimi! Ko norite, yra užtikrinimas, kad Surašymas jūsų turimas objektas galioja.

Vienas iš būdų pažvelgti į šią problemą yra pažymėti, kad Surašymas objektas yra atskiras nuo Vektorius objektas. Kadangi jie yra atskiri, jie negali susilaikyti kontroliuodami vienas kito. Šis laisvas įrišimas man leido manyti, kad galbūt naudingas kelias tyrinėjimams buvo griežčiau susietas su jį sukūrusia kolekcija.

Kuriant kolekcijas

Norint sukurti savo kolekciją, saugią siūlams, pirmiausia reikėjo kolekcijos. Mano atveju reikėjo surūšiuotos kolekcijos, bet aš nesivarginau eiti visu dvejetainiu medžiu. Vietoj to aš sukūriau kolekciją, kurią pavadinau a „SynchroList“. Šį mėnesį aš apžvelgsiu pagrindinius „SynchroList“ kolekcijos elementus ir aprašysiu, kaip juo naudotis. Kitą mėnesį 2 dalyje paimsiu kolekciją iš paprastos, lengvai suprantamos „Java“ klasės į sudėtingą daugiasriegę „Java“ klasę. Mano tikslas yra išlaikyti kolekcijos dizainą ir įgyvendinimą aiškų ir suprantamą, palyginti su metodais, naudojamais, kad ji būtų suprantama.

Aš pavadinau savo klasę „SynchroList“. Pavadinimas „SynchroList“, žinoma, kilęs iš „sinchronizavimo“ ir „sąrašo“ sujungimo. Kolekcija yra tiesiog dvigubai susietas sąrašas, kurį galite rasti bet kuriame kolegijos programavimo vadovėlyje, nors naudojant vidinę klasę, pavadintą Nuoroda, galima pasiekti tam tikrą eleganciją. Vidinė klasė Nuoroda yra apibrėžiamas taip:

 class Link {privataus objekto duomenys; privati ​​nuoroda nxt, prv; Nuoroda (objektas o, nuoroda p, nuoroda n) {nxt = n; prv = p; duomenys = o; if (n! = null) n.prv = tai; if (p! = null) p.nxt = tai; } Object getData () {grąžinti duomenis; } Susieti kitą () {return nxt; } Susieti kitą (nuoroda newNext) {nuoroda r = nxt; nxt = naujasToliau; return r;} Nuoroda prev () {return prv; } Nuoroda ankstesnė (nuoroda newPrev) {nuoroda r = prv; prv = newPrev; return r;} public String toString () {return "Nuoroda (" + duomenys + ")"; }} 

Kaip matote aukščiau pateiktame kode, a Nuoroda objektas apima susiejimo elgesį, kurį sąrašas naudos tvarkydamas savo objektus. Norėdami įgyvendinti dvigubai susieto sąrašo elgesį, objekte yra nuorodos į jo duomenų objektą, nuoroda į kitą grandinės nuorodą ir nuoroda į ankstesnę grandinės nuorodą. Toliau metodai Kitas ir prev yra perkrauta, kad būtų galima atnaujinti objekto žymeklį. Tai būtina, nes tėvų klasė turės įterpti ir ištrinti nuorodas į sąrašą. Nuorodų konstruktorius skirtas sukurti ir įterpti nuorodą tuo pačiu metu. Tai išsaugo metodo iškvietimą įgyvendinant sąrašą.

Sąraše naudojama dar viena vidinė klasė - šiuo atveju išvardijama klasė „ListEnumerator“. Ši klasė įgyvendina java.util.Skaitymas sąsaja: standartinis mechanizmas, kurį „Java“ naudoja kartodama objektų rinkinį. Leidžiant mūsų sąrankai įgyvendinti šią sąsają, mūsų kolekcija bus suderinama su bet kuriomis kitomis „Java“ klasėmis, kurios naudoja šią sąsają rinkinio turiniui išvardyti. Šios klasės įgyvendinimas parodytas žemiau esančiame kode.

 klasės „LinkEnumerator“ įgyvendina Surašymą {private Link current, previous; „LinkEnumerator“ () {current = head; } public boolean hasMoreElements () {return (current! = null); } public Object nextElement () {Object result = null; Nuoroda tmp; if (dabartinis! = null) {rezultatas = dabartinis.getData (); srovė = srovė.kitas (); } grąžinimo rezultatas; }} 

Dabartiniame įsikūnijime „LinkEnumerator“ klasė yra gana paprasta; jis taps sudėtingesnis, kai mes jį modifikuosime. Šiame įsikūnijime jis paprasčiausiai eina per skambinančio objekto sąrašą, kol jis ateina į paskutinę nuorodą vidiniame susietame sąraše. Du metodai, reikalingi programai įgyvendinti java.util.Skaitymas sąsaja yra hasMoreElements ir kitasElementas.

Žinoma, viena iš priežasčių, kodėl mes nenaudojame java.util.Vektorius klasė yra todėl, kad man reikėjo rūšiuoti kolekcijos vertes. Mes turėjome pasirinkimą: sukurti šią kolekciją, kad ji būtų būdinga tam tikram objekto tipui, taip panaudojant tas intymias žinias apie objekto tipą, norint jas rūšiuoti, arba sukurti bendresnį sprendimą, pagrįstą sąsajomis. Pasirinkau pastarąjį metodą ir apibrėžiau sąsają, pavadintą Lyginamasis apimti metodus, reikalingus objektams rūšiuoti. Ta sąsaja parodyta žemiau.

 viešoji sąsaja Palyginamasis {public loginis logotipas mažiau nei (objektas a, objektas b); viešosios loginės loginės vertės didesnė nei (objektas a, objektas b); viešoji loginė lygiavertė (objektas a, objektas b); void typeCheck (objektas a); } 

Kaip matote aukščiau pateiktame kode, Lyginamasis sąsaja yra gana paprasta. Sąsajai reikalingas vienas metodas kiekvienai iš trijų pagrindinių palyginimo operacijų. Naudojant šią sąsają, sąrašas gali palyginti objektus, kurie yra pridėti ar pašalinti, su objektais, kurie jau yra sąraše. Galutinis metodas, typeCheck, naudojamas siekiant užtikrinti kolekcijos tipo saugumą. Kai Lyginamasis naudojamas objektas, Lyginamasis gali būti naudojami apdrausti, kad visi kolekcijos objektai yra to paties tipo. Šio tipo patikrinimo vertė yra ta, kad sutaupysite matydami objektų perdavimo išimtis, jei sąraše esantis objektas nebuvo tokio tipo, kokio tikėjotės. Vėliau gavau pavyzdį, kuriame naudojamas a Lyginamasis, bet prieš pereidami prie pavyzdžio, pažvelkime į „SynchroList“ klasės tiesiogiai.

 public class SynchroList {class Link {... tai buvo parodyta aukščiau ...} class LinkEnumerator įgyvendina Surašymą {... enumerator class ...} / * Objektas mūsų elementams lyginti * / Comparator cmp; Jungties galva, uodega; public SynchroList () {} public SynchroList (Comparator c) {cmp = c; } privatus negaliojantis prieš (Object o, Link p) {new Link (o, p.prev (), p); } privatus negaliojantis po (Object o, Link p) {new Link (o, p, p.next ()); } privatus negaliojantis pašalinti (nuoroda p) {if (p.prev () == null) {head = p.next (); (p. kitas ()). prev (null); } else if (p.kitas () == null) {uodega = p.prev (); (p.prev ()). kitas (nulinis); } kitas {p.prev (). kitas (p.kitas ()); p. kitas (). prev (p.prev ()); }} public void add (Object o) {// jei cmp yra nulinis, visada pridėkite prie sąrašo galo. if (cmp == null) {if (head == null) {head = new Nuoroda (o, null, null); uodega = galva; } else {tail = nauja nuoroda (o, tail, null); } grįžti; } cmp.typeCheck (o); if (head == null) {head = new Nuoroda (o, null, null); uodega = galva; } else if (cmp.lessThan (o, head.getData ())) {head = nauja nuoroda (o, null, head); } dar {Nuoroda l; for (l = galva; l.kitas ()! = nulis; l = l.kitas ()) {if (cmp.lessThan (o, l.getData ())) {prieš (o, l); grįžti; }} tail = nauja nuoroda (o, tail, null); } grįžti; } public Boolean delete (Object o) {if (cmp == null) return false; cmp.typeCheck (o); for (Nuoroda l = galva; l! = null; l = l.next ()) {if (cmp.equalTo (o, l.getData ())) {pašalinti (l); grįžti tiesa; } if (cmp.lessThan (o, l.getData ())) pertrauka; } return false; } viešieji sinchronizuoti surašymo elementai () {grąžinti naują „LinkEnumerator“ (); } public int dydis () {int rezultatas = 0; už (nuoroda l = galva; l! = nulis; l = l.kitas ()) rezultatas ++; grąžinimo rezultatas; }} 
$config[zx-auto] not found$config[zx-overlay] not found