Programiranje

Uporaba niti z zbirkami, 1. del

Niti so sestavni del jezika Java. Z uporabo niti je do številnih algoritmov, na primer do sistemov za upravljanje čakalnih vrst, lažje dostopati, kot pa do tehnik glasovanja in zanke. Pred kratkim sem med pisanjem razreda Java ugotovil, da moram uporabljati niti med naštevanjem seznamov, kar je odkrilo nekaj zanimivih težav, povezanih z zbirkami, ki se zavedajo niti.

To Java v globino stolpec opisuje težave, ki sem jih odkril pri poskusu razvoja zbirke, varne za nit. Zbirka se imenuje "varna pred nitmi", kadar jo lahko hkrati varno uporablja več odjemalcev (niti). "Torej, v čem je težava?" vprašate. Težava je v tem, da pri običajni uporabi program spremeni zbirko (imenovano mutira) in jo prebere (imenovano naštevanje).

Nekateri preprosto ne registrirajo izjave: "Platforma Java je večnitna." Seveda, slišijo in prikimajo z glavo. Toda ne dojemajo, da so niti v Cvi, za razliko od C ali C ++, pri katerih je bilo navojanje s strani pritrjeno s strani skozi OS, osnovni jezikovni konstrukti. To napačno razumevanje ali slabo razumevanje narave Java po navoju neizogibno vodi do dveh pogostih napak v kodi Java programerjev: bodisi ne razglasijo metode za sinhronizirano, kar bi moralo biti (ker je objekt v neskladnem stanju med metode) ali razglasijo metodo za sinhronizirano, da jo zaščitijo, zaradi česar preostali sistem deluje neučinkovito.

Na to težavo sem naletel, ko sem želel zbirko, ki bi jo lahko uporabljalo več niti, ne da bi po nepotrebnem blokiral izvajanje drugih niti. Noben od razredov zbiranja v različici JDK 1.1 ni varen pred nitmi. Natančneje, noben od razredov zbirke vam ne bo omogočil naštevanja z eno nitjo, medtem ko mutirate z drugo.

Zbirke, ki niso varne za nit

Moja osnovna težava je bila naslednja: ob predpostavki, da imate urejeno zbirko predmetov, oblikujte razred Java tako, da bo nit lahko naštevala celotno ali del zbirke, ne da bi se skrbela, da bi oštevilčenje postalo neveljavno zaradi drugih niti, ki spreminjajo zbirko. Kot primer težave razmislite o Javi Vektor razred. Ta razred ni varen za nit in novim programerjem Java povzroča veliko težav, če ga kombinirajo z večnitnim programom.

The Vektor class ponuja zelo koristen pripomoček za programerje Java: in sicer dinamično veliko paleto predmetov. V praksi lahko to funkcijo uporabite za shranjevanje rezultatov, pri katerih končno število predmetov, s katerimi boste imeli opravka, ni znano, dokler ne končate z vsemi. Za prikaz tega koncepta sem zgradil naslednji primer.

01 uvoz java.util.Vector; 02 import java.util.Enumeration; 03 javni razred Demo {04 public static void main (String args []) {05 Vektorske številke = novo Vector (); 06 int rezultat = 0; 07 08 if (args.length == 0) {09 System.out.println ("Uporaba je java demo 12345"); 10 System.exit (1); 11} 12 13 za (int i = 0; i = '0') && (c <= '9')) 16 mest.addElement (novo celo število (c - '0')); 17 drugače 18 odmor; 19} 20 System.out.println ("Obstajajo" + digits.size () + "digits."); 21 za (Enumeration e = digits.elements (); e.hasMoreElements ();) {22 result = result * 10 + ((Integer) e.nextElement ()). IntValue (); 23} 24 System.out.println (args [0] + "=" + rezultat); 25 System.exit (0); 26} 27} 

Preprosti razred zgoraj uporablja a Vektor predmet za zbiranje številskih znakov iz niza. Nato je zbirka oštevilčena za izračun celoštevilčne vrednosti niza. S tem razredom ni nič narobe, razen da ni varen za nit. Če se je v drugi niti zgodila referenca na števke in ta nit je v vektor vstavila nov znak, bi bili rezultati zanke v zgornjih vrsticah od 21 do 23 nepredvidljivi. Če se je vstavljanje zgodilo, preden je objekt oštevilčenja prešel točko vstavljanja, se izračuna nit rezultat bi obdelal nov lik. Če bi se vstavljanje zgodilo po tem, ko je oštevilčenje prešlo točko vstavljanja, zanka ne bi obdelala znaka. Najslabši scenarij je, da zanka morda vrže a NoSuchElementException če je bil notranji seznam ogrožen.

Ta primer je ravno to - izmišljen primer. Prikazuje težavo, toda kakšna je možnost, da se med kratkim pet- ali šestmestnim naštevanjem zažene še ena nit? V tem primeru je tveganje majhno. Čas, ki poteče, ko ena nit začne z nevarno operacijo, ki je v tem primeru oštevilčenje, nato pa nalogo konča, se imenuje nit okno ranljivosti, ali okno. To posebno okno je znano kot dirka ker ena nit "dirka", da zaključi svojo nalogo, preden druga nit uporabi kritični vir (seznam števk). Ko pa začnete uporabljati zbirke za predstavljanje skupine več tisoč elementov, na primer z bazo podatkov, se okno ranljivosti poveča, ker bo naštevanje niti veliko več časa preživelo v svoji zankovni številki, zaradi česar se bo lahko zagnala še ena nit veliko višje. Zagotovo ne želite, da bi kakšna druga nit spreminjala seznam pod vami! Kar želite, je zagotovilo, da Naštevanje predmet, ki ga držite, je veljaven.

Eden od načinov, kako preučiti to težavo, je ugotoviti, da Naštevanje je ločen od predmeta Vektor predmet. Ker sta ločena, po ustvarjanju ne moreta obdržati nadzora drug nad drugim. Ta ohlapna vezava mi je sugerirala, da je bila morda koristna pot za raziskovanje naštevanje, ki je bilo tesneje vezano na zbirko, ki jo je ustvarila.

Ustvarjanje zbirk

Da bi ustvaril svojo zbirko, varno pred nitmi, sem najprej potreboval zbirko. V mojem primeru je bila potrebna razvrščena zbirka, vendar se nisem potrudil, da bi šel po celotni binarni drevesni poti. Namesto tega sem ustvaril zbirko, ki sem jo poimenoval a SynchroList. Ta mesec si bom ogledal ključne elemente zbirke SynchroList in opisal, kako jo uporabljati. Naslednji mesec bom v 2. delu zbirko popeljal iz preprostega, enostavno razumljivega razreda Java v zapleten večnitni razred Java. Moj cilj je, da načrt in izvedba zbirke ostaneta ločena in razumljiva glede na tehnike, ki se uporabljajo za njeno ozaveščanje.

Poimenoval sem svoj razred SynchroList. Ime "SynchroList" seveda izvira iz združevanja "sinhronizacije" in "seznama". Zbirka je preprosto dvojno povezan seznam, kot ga lahko najdete v katerem koli univerzitetnem učbeniku o programiranju, čeprav z uporabo notranjega razreda z imenom Povezava, lahko dosežemo določeno eleganco. Notranji razred Povezava je opredeljen na naslednji način:

 razred Link {zasebni podatki o objektu; zasebna povezava nxt, prv; Povezava (objekt o, povezava p, povezava n) {nxt = n; prv = p; podatki = o; if (n! = null) n.prv = to; if (p! = null) p.nxt = to; } Object getData () {vrni podatke; } Povezava naslednja () {return nxt; } Povezava naslednja (Povezava novoNaslednje) {Povezava r = nxt; nxt = novoNaslednje; vrnitev r;} Povezava prev () {vrnitev prv; } Povezava prev (Povezava newPrev) {Povezava r = prv; prv = novoPrev; return r;} javni String toString () {return "Povezava (" + podatki + ")"; }} 

Kot lahko vidite v zgornji kodi, a Povezava object zajema vezanje povezovanja, ki ga bo seznam uporabil za organizacijo svojih predmetov. Za izvajanje vezanja dvojno povezanega seznama objekt vsebuje sklice na svoj podatkovni objekt, sklic na naslednjo povezavo v verigi in sklic na prejšnjo povezavo v verigi. Nadalje metode Naslednji in prev so preobremenjeni, da omogočijo posodobitev kazalca predmeta. To je potrebno, saj bo moral nadrejeni razred vstaviti in izbrisati povezave na seznam. Konstruktor povezav je zasnovan tako, da hkrati ustvari in vstavi povezavo. To shrani klic metode pri izvedbi seznama.

Na seznamu se uporablja še en notranji razred - v tem primeru poimenovan razred preštevalca ListEnumerator. Ta razred izvaja java.util.Enumeration vmesnik: standardni mehanizem, ki ga Java uporablja za iteracijo po zbirki predmetov. Če naš števec izvede ta vmesnik, bo naša zbirka združljiva z vsemi drugimi razredi Java, ki uporabljajo ta vmesnik za naštevanje vsebine zbirke. Izvedba tega razreda je prikazana v spodnji kodi.

 razred LinkEnumerator izvaja Enumeration {private Link current, previous; LinkEnumerator () {trenutno = glava; } javno logično hasMoreElements () {return (trenutno! = null); } javni Object nextElement () {Rezultat predmeta = null; Povezava tmp; if (trenutno! = null) {rezultat = current.getData (); tok = tok.next (); } vrni rezultat; }} 

V svoji sedanji inkarnaciji je LinkEnumerator razred je precej preprost; ko ga spremenimo, bo postalo bolj zapleteno. V tej inkarnaciji se preprosto sprehodi po seznamu kličočega predmeta, dokler ne pride do zadnje povezave na notranjem povezanem seznamu. Dve metodi, potrebni za izvajanje java.util.Enumeration vmesnik hasMoreElements in nextElement.

Seveda je eden od razlogov, da ne uporabljamo java.util.Vector razred, ker sem moral razvrstiti vrednosti v zbirki. Imeli smo izbiro: zgraditi to zbirko, da bo specifična za določeno vrsto predmeta, tako da uporabimo to intimno znanje o vrsti predmeta, da jo razvrstimo, ali ustvariti bolj splošno rešitev na podlagi vmesnikov. Izbral sem slednjo metodo in določil vmesnik z imenom Primerjalnik inkapsulirati metode, potrebne za razvrščanje predmetov. Ta vmesnik je prikazan spodaj.

 javni vmesnik Primerjalnik {public boolean lessThan (objekt a, objekt b); javna logična vrednost largerThan (objekt a, objekt b); javna logična vrednost sameTo (objekt a, objekt b); void typeCheck (Predmet a); } 

Kot lahko vidite v zgornji kodi, Primerjalnik vmesnik je precej preprost. Vmesnik zahteva eno metodo za vsako od treh osnovnih operacij primerjave. S pomočjo tega vmesnika lahko seznam primerja predmete, ki jih dodajate ali odstranjujete, s predmeti, ki so že na seznamu. Končna metoda, typeCheck, se uporablja za zagotavljanje tipske varnosti zbirke. Ko Primerjalnik predmet se uporablja, Primerjalnik lahko uporabite za zagotovitev, da so vsi predmeti v zbirki iste vrste. Vrednost tega preverjanja vrste je, da vam prihrani izjeme za oddajo predmetov, če predmet na seznamu ni bil tistega tipa, ki ste ga pričakovali. Kasneje imam primer, ki uporablja Primerjalnik, toda preden pridemo do primera, si oglejmo SynchroList razred neposredno.

 javni razred SynchroList {class Link {... to je bilo prikazano zgoraj ...} class LinkEnumerator izvaja Enumeration {... razred enumerator ...} / * Predmet za primerjavo naših elementov * / Comparator cmp; Povezava glava, rep; public SynchroList () {} public SynchroList (Primerjalnik c) {cmp = c; } private void before (Object o, Link p) {new Link (o, p.prev (), p); } private void after (Object o, Link p) {new Link (o, p, p.next ()); } private void remove (Link p) {if (p.prev () == null) {head = p.next (); (p.next ()). prev (null); } sicer če (p.next () == null) {tail = p.prev (); (p.prev ()). next (null); } else {p.prev (). next (p.next ()); p.next (). prev (p.prev ()); }} public void add (Object o) {// če je cmp nič, vedno dodajte na rep seznama. if (cmp == null) {if (head == null) {head = new Link (o, null, null); rep = glava; } else {tail = nova povezava (o, tail, null); } vrnitev; } cmp.typeCheck (o); if (head == null) {head = new Link (o, null, null); rep = glava; } else if (cmp.lessThan (o, head.getData ())) {head = nova povezava (o, null, head); } else {Povezava l; for (l = head; l.next ()! = null; l = l.next ()) {if (cmp.lessThan (o, l.getData ())) {before (o, l); vrnitev; }} tail = nova povezava (o, tail, null); } vrnitev; } javno logično brisanje (objekt o) {if (cmp == null) return false; cmp.typeCheck (o); for (Link l = head; l! = null; l = l.next ()) {if (cmp.equalTo (o, l.getData ())) {remove (l); vrni res; } if (cmp.lessThan (o, l.getData ())) break; } vrne false; } javni sinhronizirani elementi štetja () {vrni nov LinkEnumerator (); } javna int velikost () {int rezultat = 0; za (Povezava l = glava; l! = nič; l = l.next ()) rezultat ++; vrniti rezultat; }} 
$config[zx-auto] not found$config[zx-overlay] not found