Programmēšana

Izmantojot pavedienus ar kolekcijām, 1. daļa

Vītnes ir neatņemama Java valodas sastāvdaļa. Izmantojot pavedienus, daudziem algoritmiem, piemēram, rindu pārvaldības sistēmām, ir vieglāk piekļūt, nekā izmantojot aptaujas un cilpu veidošanas paņēmienus. Nesen, rakstot Java klasi, es atklāju, ka, uzskaitot sarakstus, man ir jāizmanto pavedieni, un tas atklāja dažus interesantus jautājumus, kas saistīti ar pavedieniem zinošām kolekcijām.

Šis Java dziļumā kolonnā aprakstīti jautājumi, kurus atklāju, mēģinot izveidot vītnei drošu kolekciju. Kolekciju sauc par “drošu diegu”, ja to vienlaikus var droši izmantot vairāki klienti (pavedieni). "Tātad, kāda ir problēma?" tu jautā. Problēma ir tā, ka parastajā lietojumā gan programma maina kolekciju (sauktu mutējot), un to nolasa (sauc uzskaitot).

Daži cilvēki vienkārši nereģistrē paziņojumu: "Java platforma ir daudzsavienota." Protams, viņi to dzird un pamāj ar galvu. Bet viņi nesaprot, ka atšķirībā no C vai C ++, kurā vītņošana no sāniem caur OS tika pieskrūvēta, Java pavedieni ir pamata valodas konstrukcijas. Šis Java raksturīgo pavedienu rakstura pārpratums vai slikta izpratne neizbēgami noved pie diviem bieži sastopamiem trūkumiem programmētāju Java kodā: vai nu viņi nespēj deklarēt metodi kā sinhronizētu, kurai tai vajadzētu būt (jo objekta laikā metodes izpilde) vai arī viņi pasludina metodi par sinhronizētu, lai to aizsargātu, kā rezultātā pārējā sistēma darbojas neefektīvi.

Ar šo problēmu es saskāros, kad vēlējos kolekciju, kuru varēja izmantot vairāki pavedieni, nevajadzīgi bloķējot citu pavedienu izpildi. Neviena no kolekcijas klasēm JDK 1.1 versijā nav droša ar vītnēm. Neviena no kolekcijas klasēm neļaus uzskaitīt ar vienu pavedienu, vienlaikus mutējot ar citu.

Kolekcijas bez pavedieniem

Mana pamatproblēma bija šāda: Pieņemot, ka jums ir pasūtīta objektu kolekcija, izveidojiet Java klasi tā, lai pavediens varētu uzskaitīt visu kolekciju vai tās daļu, neuztraucoties par to, ka uzskaitījums kļūs nederīgs citu pavedienu dēļ, kas maina kolekciju. Kā problēmas piemēru ņemiet vērā Java Vector klasē. Šī klase nav droša ar vītnēm un rada daudz problēmu jaunajiem Java programmētājiem, kad viņi to apvieno ar daudzšķiedru programmu.

The Vector klase nodrošina ļoti noderīgu iespēju Java programmētājiem: proti, dinamiski liela izmēra objektu masīvu. Praksē jūs varat izmantot šo iespēju, lai saglabātu rezultātus, kur galīgais objektu skaits, ar kuriem jums būs darīšana, nav zināms, kamēr neesat ar tiem visiem galā. Lai parādītu šo koncepciju, es izveidoju šādu piemēru.

01 importēt java.util.Vector; 02 importēt java.util.Skaitīšana; 03 public class Demo {04 public static void main (String args []) {05 Vektora cipari = jauns Vektors (); 06 int rezultāts = 0; 07 08 if (args.length == 0) {09 System.out.println ("Lietojums ir java demo 12345"); 10 System.exit (1); 11} 12 13 (int i = 0; i = '0') && (c <= '9')) 16 cipari.addElement (jauns Integer (c - '0')); 17 vēl 18 pārtraukums; 19} 20 System.out.println ("Ir" + cipari.izmērs () + "cipari."); 21 par (Uzskaitījums e = cipari.elementi (); e.hasMoreElements ();) {22 rezultāts = rezultāts * 10 + ((Integer) e.nextElement ()). IntValue (); 23} 24 System.out.println (args [0] + "=" + rezultāts); 25 System.exit (0); 26} 27} 

Iepriekš vienkāršajā klasē tiek izmantots a Vector objektu, lai savāktu ciparu rakstzīmes no virknes. Pēc tam kolekcija tiek uzskaitīta, lai aprēķinātu virknes veselu skaitli. Šajā klasē nav nekā nepareiza, izņemot to, ka tā nav droša ar diegu. Ja kādam citam gadījumam būtu atsauce uz cipari vektoru, un šī vītne vektorā ievietoja jaunu rakstzīmi, iepriekš 21. līdz 23. rindā esošās cilpas rezultāti būtu neprognozējami. Ja ievietošana notika pirms skaitīšanas objekts bija nokārtojis ievietošanas punktu, pavediens tiek aprēķināts rezultāts apstrādātu jauno varoni. Ja ievietošana notiktu pēc tam, kad uzskaitījums būtu pagājis ievietošanas punktā, cilpa neapstrādātu rakstzīmi. Sliktākais scenārijs ir tas, ka cilpa var iemest a NoSuchElementException ja iekšējais saraksts būtu apdraudēts.

Šis piemērs ir tieši tāds - izdomāts piemērs. Tas parāda problēmu, bet kāda ir iespēja, ka īss piecu vai sešu ciparu uzskaitījums var palaist citu pavedienu? Šajā piemērā risks ir zems. Laiks, kas paiet, kad viens pavediens sāk riska darbību, kas šajā piemērā ir uzskaitījums, un pēc tam uzdevuma pabeigšanu sauc par pavediena ievainojamības logsvai logs. Šis konkrētais logs ir pazīstams kā a sacensību stāvoklis jo viens pavediens "sacenšas", lai pabeigtu savu uzdevumu, pirms cits pavediens izmanto kritisko resursu (ciparu sarakstu). Tomēr, kad sākat izmantot kolekcijas, lai attēlotu vairāku tūkstošu elementu grupu, piemēram, ar datu bāzi, ievainojamības logs palielinās, jo pavedienu uzskaitīšana pavadīs daudz vairāk laika tās uzskaites lokā, un tas dod iespēju darboties citai pavedienai daudz augstāk. Jūs noteikti nevēlaties, lai kāds cits pavediens maina sarakstu zem jums! Ko vēlaties, ir pārliecība, ka Uzskaitīšana objekts, kuru turat, ir derīgs.

Viens no veidiem, kā apskatīt šo problēmu, ir atzīmēt, ka Uzskaitīšana objekts ir atsevišķi no Vector objekts. Tā kā viņi ir atsevišķi, viņi pēc izveidošanas nespēj saglabāt kontroli pār otru. Šī brīvā iesiešana man ieteica, ka varbūt noderīgs izpētes ceļš ir uzskaitījums, kas ciešāk saistīts ar kolekciju, kas to veidojusi.

Kolekciju veidošana

Lai izveidotu savu vītnei nekaitīgo kolekciju, man vispirms bija nepieciešama kolekcija. Manā gadījumā bija nepieciešama šķirota kolekcija, taču es neuztraucos iet visu binārā koka maršrutu. Tā vietā es izveidoju kolekciju, kuru saucu par SynchroList. Šomēnes apskatīšu SynchroList kolekcijas pamatelementus un aprakstīšu, kā to izmantot. Nākamajā mēnesī 2. daļā es pārnesu kolekciju no vienkāršas, viegli saprotamas Java klases uz sarežģītu vairāku pavedienu Java klasi. Mans mērķis ir saglabāt kolekcijas dizainu un ieviešanu atšķirīgu un saprotamu salīdzinājumā ar tehniku, kas tiek izmantota, lai padarītu to informētu par pavedieniem.

Es nosaucu savu klasi SynchroList. Nosaukums "SynchroList", protams, nāk no "sinhronizācijas" un "saraksta" savienošanas. Kolekcija ir vienkārši divkārši saistīts saraksts, kā jūs varat atrast jebkurā koledžas mācību grāmatā par programmēšanu, kaut arī izmantojot iekšējo klasi ar nosaukumu Saite, var panākt noteiktu eleganci. Iekšējā klase Saite ir definēts šādi:

 klases saite {privātā objekta dati; privātā saite nxt, prv; Saite (objekts o, saite p, saite n) {nxt = n; prv = p; dati = o; ja (n! = null) n.prv = šis; ja (p! = null) p.nxt = šis; } Object getData () {atgriešanas dati; } Saite blakus () {return nxt; } Saite nākamā (Saite newNext) {Saite r = nxt; nxt = newNext; atgriešanās r;} Saite prev () {atgriešanās prv; } Saites iepriekšējais (Link newPrev) {Saite r = prv; prv = newPrev; return r;} public String toString () {return "Saite (" + dati + ")"; }} 

Kā redzat iepriekš redzamajā kodā, a Saite objekts ietver saistīšanas darbību, kuru saraksts izmantos, lai sakārtotu savus objektus. Lai ieviestu divkārši saistītā saraksta darbību, objektā ir atsauces uz tā datu objektu, atsauce uz nākamo saiti ķēdē un atsauce uz iepriekšējo saiti ķēdē. Tālāk metodes Nākamais un prev ir pārslogoti, lai nodrošinātu līdzekli objekta rādītāja atjaunināšanai. Tas ir nepieciešams, jo vecāku klasei būs jāievieto un jāizdzēš saites sarakstā. Saites konstruktors ir paredzēts, lai vienlaikus izveidotu un ievietotu saiti. Tas saglabā metodes izsaukumu saraksta ieviešanā.

Sarakstā tiek izmantota vēl viena iekšējā klase - šajā gadījumā nosaukta skaitītāja klase ListEnumerator. Šī klase īsteno java.util.Skaitīšana interfeiss: standarta mehānisms, ko Java izmanto, lai atkārtotu objektu kolekciju. Liekot mūsu skaitītājam ieviest šo saskarni, mūsu kolekcija būs saderīga ar visām citām Java klasēm, kuras izmanto šo saskarni, lai uzskaitītu kolekcijas saturu. Šīs klases ieviešana ir parādīta zemāk esošajā kodā.

 klase LinkEnumerator īsteno uzskaitījumu {private Link current, previous; LinkEnumerator () {pašreizējais = galva; } public Boolean hasMoreElements () {return (current! = null); } public Object nextElement () {Object result = null; Saite tmp; if (pašreizējais! = null) {rezultāts = pašreizējais.getData (); strāva = strāva.nākamais (); } atgriešanās rezultāts; }} 

Pašreizējā iemiesojumā LinkEnumerator klase ir diezgan vienkārša; tas kļūs sarežģītāks, kad mēs to modificēsim. Šajā iemiesojumā tā vienkārši iet cauri izsaucošā objekta sarakstam, līdz nonāk iekšējā saistītā saraksta pēdējā saitē. Divas metodes, kas nepieciešamas, lai ieviestu java.util.Skaitīšana saskarne ir hasMoreElements un nextElement.

Protams, viens no iemesliem, kāpēc mēs neizmantojam java.util.Vektors klase ir tāpēc, ka man vajadzēja sakārtot vērtības kolekcijā. Mums bija izvēle: veidot šo kolekciju, lai tā būtu raksturīga konkrētam objekta tipam, tādējādi izmantojot šīs intīmās zināšanas par objekta tipu, lai tās šķirotu, vai arī izveidot vispārīgāku risinājumu, kura pamatā ir saskarnes. Es izvēlējos pēdējo metodi un definēju interfeisu ar nosaukumu Salīdzinātājs iekapsulēt objektu šķirošanai nepieciešamās metodes. Šī saskarne ir parādīta zemāk.

 publiskā saskarne Salīdzinātājs {public boolean lessThan (Object a, Object b); publiskais būla lielumsThan (objekts a, objekts b); publiskais būla skaitlis equTo (objekts a, objekts b); void typeCheck (objekts a); } 

Kā redzat iepriekš minētajā kodā, Salīdzinātājs interfeiss ir diezgan vienkāršs. Saskarnei ir nepieciešama viena metode katrai no trim pamata salīdzināšanas darbībām. Izmantojot šo saskarni, saraksts var salīdzināt objektus, kas tiek pievienoti vai noņemti, ar objektiem, kas jau ir sarakstā. Galīgā metode, typeCheck, tiek izmantots, lai nodrošinātu kolekcijas veida drošību. Kad Salīdzinātājs objekts tiek izmantots, Salīdzinātājs var izmantot, lai apdrošinātu, ka visi kolekcijas objekti ir viena veida. Šāda veida pārbaudes vērtība ir tā, ka tas ietaupa objektu liešanas izņēmumu skatīšanu, ja sarakstā esošais objekts nebija tāda veida, kādu gaidījāt. Man vēlāk ir piemērs, kurā tiek izmantots Salīdzinātājs, bet pirms mēs nonākam pie piemēra, apskatīsim SynchroList klasē tieši.

 public class SynchroList {class Link {... tas tika parādīts iepriekš ...} klase LinkEnumerator ievieš uzskaitījumu {... enumerator class ...} / * objekts mūsu elementu salīdzināšanai * / Comparator cmp; Saites galva, aste; public SynchroList () {} public SynchroList (Comparator c) {cmp = c; } private void before (Object o, Link p) {new Saite (o, p.prev (), p); } private void aiz (Object o, Link p) {new Link (o, p, p.next ()); } private void noņemt (Saite p) {if (p.prev () == null) {head = p.next (); (p.nākamais ()). prev (nulle); } else if (p.next () == null) {aste = p.prev (); (p.prev ()). nākamais (null); } cits {p.prev (). nākamais (p.nākamais ()); p.nxt (). prev (p.prev ()); }} public void add (Object o) {// ja cmp ir nulle, vienmēr pievienojiet to saraksta astei. if (cmp == null) {if (head == null) {head = new Saite (o, null, null); aste = galva; } else {aste = jauna saite (o, aste, nulle); } atgriešanās; } cmp.typeCheck (o); if (head == null) {head = new Saite (o, null, null); aste = galva; } else if (cmp.lessThan (o, head.getData ())) {head = new Saite (o, null, head); } cits {Saite l; for (l = galva; l.next ()! = null; l = l.next ()) {if (cmp.lessThan (o, l.getData ())) {pirms (o, l); atgriešanās; }} aste = jauna saite (o, aste, nulle); } atgriešanās; } public boolean delete (Object o) {if (cmp == null) return false; cmp.typeCheck (o); for (Saite l = galva; l! = null; l = l.next ()) {if (cmp.equalTo (o, l.getData ())) {noņemt (l); atgriezties taisnība; } if (cmp.lessThan (o, l.getData ())) pārtraukums; } return false; } publiski sinhronizēti uzskaites elementi () {return new LinkEnumerator (); } public int size () {int rezultāts = 0; par (Saite l = galva; l! = nulle; l = l.next ()) rezultāts ++; atgriešanās rezultāts; }} 
$config[zx-auto] not found$config[zx-overlay] not found