Programmering

Design til trådsikkerhed

For seks måneder siden begyndte jeg en serie artikler om design af klasser og objekter. I denne måneds Designteknikker kolonne, vil jeg fortsætte den serie ved at se på designprincipper, der vedrører trådsikkerhed. Denne artikel fortæller dig, hvad trådsikkerhed er, hvorfor du har brug for det, når du har brug for det, og hvordan du kan få fat i det.

Hvad er trådsikkerhed?

Trådsikkerhed betyder simpelthen, at felterne i et objekt eller en klasse altid opretholder en gyldig tilstand, som det observeres af andre objekter og klasser, selv når de bruges samtidigt af flere tråde.

En af de første retningslinjer, jeg foreslog i denne kolonne (se "Designe initialisering af objekt") er, at du skal designe klasser, så objekter opretholder en gyldig tilstand fra begyndelsen af ​​deres levetid til slutningen. Hvis du følger dette råd og opretter objekter, hvis forekomstvariabler alle er private, og hvis metoder kun foretager korrekte tilstandsovergange på disse forekomstvariabler, er du i god form i et miljø med en enkelt tråd. Men du kan komme i problemer, når der kommer flere tråde.

Flere tråde kan stave problemer for dit objekt, fordi tilstanden af ​​dit objekt ofte kan være ugyldig, mens en metode er i færd med at udføres. Når kun en tråd påkalder objektets metoder, udføres kun en metode ad gangen nogensinde, og hver metode får lov til at afslutte, før en anden metode påberåbes. I et enkelt trådmiljø får hver metode således en chance for at sikre, at enhver midlertidigt ugyldig tilstand ændres til en gyldig tilstand, før metoden vender tilbage.

Når du først har introduceret flere tråde, kan JVM dog afbryde tråden, der udfører en metode, mens objektets instansvariabler stadig er i en midlertidig ugyldig tilstand. JVM kunne derefter give en anden tråd en chance for at udføre, og den tråd kunne kalde en metode på det samme objekt. Alt dit hårde arbejde for at gøre dine instansvariabler private, og dine metoder udfører kun gyldige tilstandstransformationer, vil ikke være nok til at forhindre denne anden tråd i at observere objektet i en ugyldig tilstand.

Et sådant objekt ville ikke være trådsikkert, for i et multitrådet miljø kunne objektet blive ødelagt eller blive observeret i en ugyldig tilstand. Et trådsikkert objekt er et objekt, der altid opretholder en gyldig tilstand, som det observeres af andre klasser og objekter, selv i et multitrådet miljø.

Hvorfor bekymre sig om trådsikkerhed?

Der er to store grunde til, at du skal tænke på trådsikkerhed, når du designer klasser og objekter i Java:

  1. Support til flere tråde er indbygget i Java-sprog og API

  2. Alle tråde inde i en Java virtuel maskine (JVM) deler det samme bunke- og metodeområde

Fordi multithreading er indbygget i Java, er det muligt, at enhver klasse, du designer, i sidste ende kan bruges samtidigt af flere tråde. Du behøver ikke (og bør ikke) gøre hver klasse, du designer, trådsikker, fordi trådsikkerhed ikke kommer gratis. Men det skal du i det mindste tænke om trådsikkerhed hver gang du designer en Java-klasse. Du finder en diskussion af omkostningerne ved trådsikkerhed og retningslinjer for, hvornår du skal gøre klasser trådsikre senere i denne artikel.

I betragtning af JVM's arkitektur behøver du kun være bekymret for instans- og klassevariabler, når du bekymrer dig om trådsikkerhed. Da alle tråde deler den samme bunke, og bunken er, hvor alle instansvariabler er gemt, kan flere tråde forsøge at bruge det samme objekts instansvariabler samtidigt. På samme måde, fordi alle tråde deler det samme metodeområde, og metodeområdet er, hvor alle klassevariabler er gemt, kan flere tråde forsøge at bruge de samme klassevariabler samtidigt. Når du vælger at gøre en klassetrådsikker, er dit mål at garantere integriteten - i et multitrådet miljø - af instanser og klassevariabler, der er erklæret i denne klasse.

Du behøver ikke bekymre dig om multitrådet adgang til lokale variabler, metodeparametre og returværdier, fordi disse variabler findes på Java-stakken. I JVM tildeles hver tråd sin egen Java-stak. Ingen tråd kan se eller bruge lokale variabler, returværdier eller parametre, der tilhører en anden tråd.

I betragtning af JVM's struktur er lokale variabler, metodeparametre og returværdier i sagens natur "trådsikre". Men instansvariabler og klassevariabler er kun trådsikre, hvis du designer din klasse korrekt.

RGBColor # 1: Klar til en enkelt tråd

Som et eksempel på en klasse, der er ikke trådsikker, overvej RGBColor klasse, vist nedenfor. Forekomster af denne klasse repræsenterer en farve, der er gemt i tre private forekomstvariabler: r, gog b. I betragtning af klassen vist nedenfor er en RGBColor objekt ville begynde sit liv i en gyldig tilstand og ville kun opleve gyldige tilstandsovergange fra begyndelsen af ​​dets liv til slutningen - men kun i et miljø med en enkelt tråd.

// I filtråde / ex1 / RGBColor.java // Forekomster af denne klasse er IKKE trådsikre. offentlig klasse RGBColor {privat int r; privat int g; privat int b; offentlig RGBColor (int r, int g, int b) {checkRGBVals (r, g, b); this.r = r; this.g = g; this.b = b; } public void setColor (int r, int g, int b) {checkRGBVals (r, g, b); this.r = r; this.g = g; this.b = b; } / ** * returnerer farve i en matrix på tre inte: R, G og B * / public int [] getColor () {int [] retVal = new int [3]; retVal [0] = r; retVal [1] = g; retVal [2] = b; returnere retVal; } offentlig ugyldig invert () {r = 255 - r; g = 255 - g; b = 255 - b; } privat statisk ugyldig checkRGBVals (int r, int g, int b) {if (r 255 || g 255 || b <0 || b> 255) {kast nyt IllegalArgumentException (); }}} 

Fordi de tre instansvariabler, ints r, gog b, er private, den eneste måde, hvorpå andre klasser og objekter kan få adgang til eller påvirke værdierne for disse variabler, er via RGBColors konstruktør og metoder. Konstruktionsdesignet og metoderne garanterer at:

  1. RGBColors konstruktør giver altid variablerne korrekte startværdier

  2. Metoder setColor () og invertere () vil altid udføre gyldige tilstandstransformationer på disse variabler

  3. Metode getColor () vil altid returnere en gyldig visning af disse variabler

Bemærk, at hvis dårlige data sendes til konstruktøren eller setColor () metode, udfyldes de brat med en InvalidArgumentException. Det checkRGBVals () metode, der kaster denne undtagelse, definerer i virkeligheden, hvad det betyder for en RGBColor modstand skal være gyldig: værdierne for alle tre variabler, r, gog b, skal være mellem 0 og 255 inklusive. For at være gyldig skal farven, der er repræsenteret af disse variabler, være den nyeste farve, enten sendt til konstruktøren eller setColor () metode eller fremstillet af invertere () metode.

Hvis du i et enkelt trådmiljø påberåber dig setColor () og passere i blåt, den RGBColor objektet vil være blåt når setColor () vender tilbage. Hvis du derefter påberåber dig getColor () på det samme objekt bliver du blå. I et samfund med en enkelt tråd, forekomster af dette RGBColor klasse er velopdragne.

At kaste en samtidig skruenøgle i værkerne

Desværre er dette lykkelige billede af en velopdragen RGBColor objekt kan blive skræmmende, når andre tråde kommer ind i billedet. I et multitrådet miljø forekomster af RGBColor klasse defineret ovenfor er modtagelige for to slags dårlig opførsel: skrive / skrive konflikter og læse / skrive konflikter.

Skriv / skriv konflikter

Forestil dig, at du har to tråde, en tråd med navnet "rød" og en anden med navnet "blå". Begge tråde forsøger at indstille farven på den samme RGBColor objekt: Den røde tråd forsøger at indstille farven til rød; den blå tråd forsøger at indstille farven til blå.

Begge disse tråde forsøger at skrive til det samme objekts instansvariabler samtidigt. Hvis trådplanlæggeren sammenfletter disse to tråde på den helt rigtige måde, vil de to tråde uforvarende interferere med hinanden og give en skriv / skriv-konflikt. I processen vil de to tråde ødelægge objektets tilstand.

Det Usynkroniseret RGBColor applet

Den følgende applet, navngivet Usynkroniseret RGBColor, demonstrerer en række begivenheder, der kan resultere i en korrupt RGBColor objekt. Den røde tråd forsøger uskyldigt at indstille farven til rød, mens den blå tråd uskyldigt forsøger at indstille farven til blå. I sidste ende, den RGBColor objekt repræsenterer hverken rød eller blå, men den foruroligende farve, magenta.

Af en eller anden grund vil din browser ikke lade dig se denne måde seje Java-applet.

At gå igennem rækkefølgen af ​​begivenheder, der fører til en ødelagt RGBColor objekt, skal du trykke på applets Step-knap. Tryk på Tilbage for at sikkerhedskopiere et trin og Nulstil for at sikkerhedskopiere til begyndelsen. Når du går, forklarer en tekstlinje nederst i appleten, hvad der sker under hvert trin.

For de af jer, der ikke kan køre appleten, her er en tabel, der viser rækkefølgen af ​​begivenheder demonstreret af appleten:

TrådUdmeldingrgbFarve
ingenobjekt repræsenterer grønt02550 
blåblå tråd påkalder setColor (0, 0, 255)02550 
blåcheckRGBVals (0, 0, 255);02550 
blåthis.r = 0;02550 
blåthis.g = 0;02550 
blåblå bliver forhindret000 
rødrød tråd påkalder setColor (255, 0, 0)000 
rødcheckRGBVals (255, 0, 0);000 
rødthis.r = 255;000 
rødthis.g = 0;25500 
rødthis.b = 0;25500 
rødrød tråd vender tilbage25500 
blåsenere fortsætter den blå tråd25500 
blåthis.b = 25525500 
blåblå tråd vender tilbage2550255 
ingenobjekt repræsenterer magenta2550255 

Som du kan se fra denne applet og tabel, er RGBColor er beskadiget, fordi trådplanlæggeren afbryder den blå tråd, mens objektet stadig er i en midlertidig ugyldig tilstand. Når den røde tråd kommer ind og maler objektet rødt, er den blå tråd kun delvist færdig med at male objektet blå. Når den blå tråd vender tilbage for at afslutte jobbet, ødelægger den utilsigtet objektet.

Læs / skriv konflikter

En anden form for dårlig opførsel, der kan udstilles i et multitrådet miljø ved tilfælde af dette RGBColor klasse er læse / skrive konflikter. Denne form for konflikt opstår, når et objekts tilstand læses og bruges i en midlertidigt ugyldig tilstand på grund af en anden tråds ufærdige arbejde.

Bemærk for eksempel, at under den blå tråds udførelse af setColor () Metoden ovenfor finder objektet på et tidspunkt sig i den midlertidigt ugyldige tilstand af sort. Her er sort en midlertidig ugyldig tilstand, fordi:

  1. Det er midlertidigt: Til sidst har den blå tråd til hensigt at indstille farven til blå.

  2. Det er ugyldigt: Ingen bad om en sort RGBColor objekt. Den blå tråd skal forme en grøn genstand til blå.

Hvis den blå tråd forhindres i det øjeblik, repræsenterer objektet sort med en tråd, der påberåber sig getColor () på det samme objekt, ville den anden tråd observere RGBColor objektets værdi er sort.

Her er en tabel, der viser en række begivenheder, der kan føre til netop en sådan læse / skrive konflikt:

TrådUdmeldingrgbFarve
ingenobjekt repræsenterer grønt02550 
blåblå tråd påkalder setColor (0, 0, 255)02550 
blåcheckRGBVals (0, 0, 255);02550 
blåthis.r = 0;02550 
blåthis.g = 0;02550 
blåblå bliver forhindret000 
rødrød tråd påberåber sig getColor ()000 
rødint [] retVal = ny int [3];000 
rødretVal [0] = 0;000 
rødretVal [1] = 0;000 
rødretVal [2] = 0;000 
rødreturnere retVal;000 
rødrød tråd returnerer sort000 
blåsenere fortsætter den blå tråd000 
blåthis.b = 255000 
blåblå tråd vender tilbage00255 
ingenobjekt repræsenterer blå00255 

Som du kan se fra denne tabel, begynder problemet, når den blå tråd afbrydes, når den kun delvis er færdig med at male objektet blåt. På dette tidspunkt er objektet i en midlertidig ugyldig tilstand af sort, hvilket er præcis, hvad den røde tråd ser, når den påberåber sig getColor () på objektet.

Tre måder at gøre et objekt trådsikkert

Der er dybest set tre tilgange, du kan tage for at lave et objekt som f.eks RGBTråd trådsikker:

  1. Synkroniser kritiske sektioner
  2. Gør det uforanderligt
  3. Brug en trådsikker indpakning

Metode 1: Synkronisering af de kritiske sektioner

Den mest ligefremme måde at rette op på den uregerlige opførsel, der udstilles af genstande som f.eks RGBColor når det placeres i en multitrådet kontekst, er at synkronisere objektets kritiske sektioner. En genstand er kritiske sektioner er disse metoder eller kodeblokke inden for metoder, der kun skal udføres af en tråd ad gangen. Sagt på en anden måde: et kritisk afsnit er en metode eller blok af kode, der skal udføres atomisk, som en enkelt, udelelig operation. Ved hjælp af Java'er synkroniseret nøgleord, kan du garantere, at kun en tråd ad gangen nogensinde vil udføre objektets kritiske sektioner.

For at tage denne tilgang til at gøre dit objekt trådsikkert skal du følge to trin: du skal gøre alle relevante felter private, og du skal identificere og synkronisere alle de kritiske sektioner.

Trin 1: Gør felter private

Synkronisering betyder, at kun en tråd ad gangen er i stand til at udføre en smule kode (et kritisk afsnit). Så selvom det er felter du ønsker at koordinere adgang til blandt flere tråde, Java's mekanisme til at gøre det koordinerer faktisk adgang til kode. Dette betyder, at kun hvis du gør dataene private, vil du være i stand til at kontrollere adgangen til disse data ved at kontrollere adgangen til den kode, der manipulerer dataene.