Programmering

Design for trådsikkerhet

For seks måneder siden begynte jeg på en serie artikler om å designe klasser og objekter. I denne månedens Designteknikker kolonne, fortsetter jeg serien ved å se på designprinsipper som gjelder trådsikkerhet. Denne artikkelen forteller deg hva trådsikkerhet er, hvorfor du trenger det, når du trenger det, og hvordan du kan få tak i det.

Hva er trådsikkerhet?

Trådsikkerhet betyr ganske enkelt at feltene til et objekt eller klasse alltid opprettholder en gyldig tilstand, slik det observeres av andre objekter og klasser, selv når de brukes samtidig av flere tråder.

En av de første retningslinjene jeg foreslo i denne kolonnen (se "Designing initialization initialization") er at du skal utforme klasser slik at objekter opprettholder en gyldig tilstand, fra begynnelsen av deres levetid til slutten. Hvis du følger dette rådet og oppretter objekter hvis forekomstvariabler alle er private og hvis metoder bare gjør riktig tilstandsovergang på disse forekomstvariablene, er du i god form i et miljø med en tråd. Men du kan komme i trøbbel når flere tråder kommer.

Flere tråder kan stave problemer for objektet ditt fordi ofte, mens en metode er i ferd med å kjøres, kan tilstanden til objektet være midlertidig ugyldig. Når bare en tråd påkaller objektets metoder, vil bare en metode av gangen utføres, og hver metode får fullføre før en annen metode påkalles. I et enkelt trådmiljø vil hver metode bli gitt en sjanse til å sikre at enhver midlertidig ugyldig tilstand blir endret til en gyldig tilstand før metoden returnerer.

Når du først har introdusert flere tråder, kan JVM imidlertid avbryte tråden som utfører en metode mens objektets forekomstvariabler fortsatt er i en midlertidig ugyldig tilstand. JVM kunne da gi en annen tråd en sjanse til å utføre, og den tråden kunne kalle en metode på det samme objektet. Alt ditt harde arbeid for å gjøre forekomstvariablene dine private og metodene dine utfører bare gyldige tilstandstransformasjoner, vil ikke være nok til å forhindre at denne andre tråden observerer objektet i ugyldig tilstand.

Et slikt objekt ville ikke være trådsikkert, for i et flertrådet miljø kan objektet bli ødelagt eller bli observert å ha en ugyldig tilstand. Et trådsikkert objekt er en som alltid opprettholder en gyldig tilstand, slik den observeres av andre klasser og objekter, selv i et flertrådet miljø.

Hvorfor bekymre deg for trådsikkerhet?

Det er to store grunner til at du trenger å tenke på trådsikkerhet når du designer klasser og objekter i Java:

  1. Støtte for flere tråder er innebygd i Java-språket og API

  2. Alle tråder i en Java Virtual Machine (JVM) har samme haug- og metodeområde

Fordi multitrading er innebygd i Java, er det mulig at enhver klasse du designer til slutt kan brukes samtidig av flere tråder. Du trenger ikke (og bør ikke) gjøre hver klasse du designer trådsikker, fordi trådsikkerhet ikke kommer gratis. Men du bør i det minste synes at om trådsikkerhet hver gang du designer en Java-klasse. Du finner en diskusjon om kostnadene ved trådsikkerhet og retningslinjer for når du skal gjøre klasser trådsikre senere i denne artikkelen.

Gitt JVMs arkitektur, trenger du bare være opptatt av forekomst- og klassevariabler når du bekymrer deg for trådsikkerhet. Fordi alle tråder deler samme bunke, og bunken er der alle forekomstvariabler er lagret, kan flere tråder forsøke å bruke det samme objektets forekomstvariabler samtidig. På samme måte, fordi alle tråder deler samme metodeområde, og metodeområdet er der alle klassevariabler er lagret, kan flere tråder forsøke å bruke de samme klassevariablene samtidig. Når du velger å gjøre en klassetrådsikker, er målet ditt å garantere integriteten - i et flertrådet miljø - for eksempel og klassevariabler som er erklært i den klassen.

Du trenger ikke bekymre deg for flertrådet tilgang til lokale variabler, metodeparametere og returverdier, fordi disse variablene ligger på Java-stakken. I JVM tildeles hver tråd sin egen Java-stabel. Ingen tråder kan se eller bruke lokale variabler, returverdier eller parametere som tilhører en annen tråd.

Gitt strukturen til JVM er lokale variabler, metodeparametere og returverdier iboende "trådsikre". Men forekomstvariabler og klassevariabler vil bare være trådsikre hvis du designer klassen din riktig.

RGBColor # 1: Klar for en enkelt tråd

Som et eksempel på en klasse som er ikke trådsikker, vurder RGBColor klasse, vist nedenfor. Forekomster av denne klassen representerer en farge som er lagret i tre private forekomstvariabler: r, g, og b. Gitt klassen vist nedenfor, an RGBColor objektet ville begynne sitt liv i en gyldig tilstand og ville kun oppleve gyldige tilstandsoverganger, fra begynnelsen av sitt liv til slutten - men bare i et miljø med en tråd.

// I filtråder / ex1 / RGBColor.java // Forekomster av denne klassen er IKKE trådsikre. offentlig klasse RGBColor {privat int r; private 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 farge i en matrise med tre inter: R, G og B * / public int [] getColor () {int [] retVal = new int [3]; retVal [0] = r; retVal [1] = g; retVal [2] = b; retur retVal; } public void invert () {r = 255 - r; g = 255 - g; b = 255 - b; } privat statisk ugyldig sjekkRGBVals (int r, int g, int b) {if (r 255 || g 255 || b <0 || b> 255) {throw new IllegalArgumentException (); }}} 

Fordi de tre forekomstvariablene, ints r, g, og b, er private, den eneste måten andre klasser og objekter kan få tilgang til eller påvirke verdiene til disse variablene er via RGBColorsin konstruktør og metoder. Utformingen av konstruktøren og metodene garanterer at:

  1. RGBColorsin konstruktør vil alltid gi variablene riktige startverdier

  2. Metoder setColor () og invertere () vil alltid utføre gyldige tilstandstransformasjoner på disse variablene

  3. Metode getColor () vil alltid returnere en gyldig visning av disse variablene

Merk at hvis dårlige data sendes til konstruktøren eller setColor () metoden, vil de fullføre brått med en InvalidArgumentException. De checkRGBVals () metode, som kaster dette unntaket, definerer faktisk hva det betyr for en RGBColor innvending som skal være gyldig: verdiene til alle tre variablene, r, g, og b, må være mellom 0 og 255, inkludert. I tillegg, for å være gyldig, må fargen representert av disse variablene være den nyeste fargen, enten sendt til konstruktøren eller setColor () metode, eller produsert av invertere () metode.

Hvis du i et enkelt trådmiljø påberoper deg setColor () og passere i blått, den RGBColor objektet vil være blått når setColor () returnerer. Hvis du deretter påkaller getColor () på samme objekt, blir du blå. I et enkelt trådssamfunn, forekomster av dette RGBColor klasse er veloppdragen.

Kast en skiftenøkkel i verkene

Dessverre dette lykkelige bildet av en veloppdragen RGBColor objektet kan bli skummelt når andre tråder kommer inn i bildet. I et flertrådet miljø, forekomster av RGBColor klasse definert ovenfor er utsatt for to typer dårlig oppførsel: skrive / skrive konflikter og lese / skrive konflikter.

Skriv / skriv konflikter

Tenk deg at du har to tråder, en tråd som heter "rød" og en annen som heter "blå". Begge trådene prøver å sette fargen på det samme RGBColor objekt: Den røde tråden prøver å sette fargen til rød; den blå tråden prøver å sette fargen til blå.

Begge disse trådene prøver å skrive til det samme objektets forekomstvariabler samtidig. Hvis trådplanleggeren fletter inn disse to trådene på riktig måte, vil de to trådene uforvarende forstyrre hverandre og gi en skriv / skriv-konflikt. I prosessen vil de to trådene ødelegge objektets tilstand.

De Usynkronisert RGBColor applet

Følgende applet, kalt Usynkronisert RGBColor, demonstrerer en rekke hendelser som kan resultere i en korrupt RGBColor gjenstand. Den røde tråden prøver uskyldig å sette fargen til rød, mens den blå tråden uskyldig prøver å sette fargen til blå. Til slutt, RGBColor objektet representerer verken rødt eller blått, men den foruroligende fargen magenta.

Av en eller annen grunn, vil nettleseren din ikke la deg se denne kule Java-appleten.

Å gå gjennom hendelsesforløpet som fører til en ødelagt RGBColor objekt, trykk appletens trinn-knapp. Trykk på Tilbake for å sikkerhetskopiere et trinn, og Tilbakestill for å sikkerhetskopiere til begynnelsen. Når du går, vil en tekstlinje nederst i appleten forklare hva som skjer under hvert trinn.

For de av dere som ikke kan kjøre appleten, her er en tabell som viser rekkefølgen av hendelser demonstrert av appleten:

TrådUttalelsergbFarge
ingenobjektet representerer grønt02550 
blåblå tråd påkaller setColor (0, 0, 255)02550 
blåcheckRGBVals (0, 0, 255);02550 
blåthis.r = 0;02550 
blåthis.g = 0;02550 
blåblå blir forhåndsbestemt000 
rødrød tråd påkaller 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 kommer tilbake25500 
blåsenere fortsetter blå tråd25500 
blåthis.b = 25525500 
blåblå tråd kommer tilbake2550255 
ingenobjektet representerer magenta2550255 

Som du kan se fra denne appleten og tabellen, er RGBColor er ødelagt fordi trådplanleggeren avbryter den blå tråden mens objektet fremdeles er i en midlertidig ugyldig tilstand. Når den røde tråden kommer inn og maler gjenstanden rød, er den blå tråden bare delvis ferdig med å male gjenstanden blå. Når den blå tråden kommer tilbake for å fullføre jobben, ødelegger den utilsiktet objektet.

Lese / skrive konflikter

En annen slags dårlig oppførsel som kan vises i et flertrådet miljø av tilfeller av dette RGBColor klasse er lese / skrive konflikter. Denne typen konflikt oppstår når en objekts tilstand leses og brukes i en midlertidig ugyldig tilstand på grunn av det uferdige arbeidet til en annen tråd.

Merk for eksempel at under utførelsen av den blå tråden setColor () Metoden ovenfor, finner objektet på et tidspunkt seg i den midlertidig ugyldige tilstanden svart. Her er svart en midlertidig ugyldig tilstand fordi:

  1. Det er midlertidig: Til slutt har den blå tråden til hensikt å sette fargen på blå.

  2. Det er ugyldig: Ingen ba om en svart RGBColor gjenstand. Den blå tråden skal visstnok gjøre en grønn gjenstand til blå.

Hvis den blå tråden er forhåndsbestemt for øyeblikket, representerer objektet svart med en tråd som påkaller getColor () på den samme gjenstanden, ville den andre tråden observere RGBColor objektets verdi for å være svart.

Her er en tabell som viser en rekke hendelser som kan føre til akkurat en slik lese / skrive konflikt:

TrådUttalelsergbFarge
ingenobjektet representerer grønt02550 
blåblå tråd påkaller setColor (0, 0, 255)02550 
blåcheckRGBVals (0, 0, 255);02550 
blåthis.r = 0;02550 
blåthis.g = 0;02550 
blåblå blir forhåndsbestemt000 
rødrød tråd påkaller 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ødretur retVal;000 
rødrød tråd returnerer svart000 
blåsenere fortsetter blå tråd000 
blåthis.b = 255000 
blåblå tråd kommer tilbake00255 
ingenobjektet representerer blått00255 

Som du kan se fra denne tabellen, begynner problemet når den blå tråden blir avbrutt når den bare delvis har malt objektet blått. På dette punktet er objektet i en midlertidig ugyldig tilstand av svart, som er nøyaktig hva den røde tråden ser når den påkaller getColor () på objektet.

Tre måter å gjøre et objekt trådsikkert

Det er i utgangspunktet tre tilnærminger du kan ta for å lage et objekt som RGBTråd trådsikker:

  1. Synkroniser kritiske seksjoner
  2. Gjør det uforanderlig
  3. Bruk en trådsikker innpakning

Tilnærming 1: Synkronisering av kritiske seksjoner

Den mest enkle måten å korrigere uregjerlig oppførsel utstilt av objekter som RGBColor når den plasseres i en flertrådet sammenheng, er å synkronisere objektets kritiske seksjoner. Et objekt kritiske seksjoner er de metodene eller kodeblokkene innen metoder som bare må utføres av en tråd om gangen. Sagt på en annen måte, en kritisk seksjon er en metode eller blokk med kode som må utføres atomisk, som en enkelt, udelelig operasjon. Ved å bruke Java-er synkronisert nøkkelord, kan du garantere at bare en tråd om gangen noen gang vil utføre objektets kritiske seksjoner.

For å ta denne tilnærmingen til å gjøre objektet ditt trådsikkert, må du følge to trinn: du må gjøre alle relevante felt private, og du må identifisere og synkronisere alle de kritiske delene.

Trinn 1: Gjør felt private

Synkronisering betyr at bare en tråd om gangen vil kunne utføre litt kode (en kritisk del). Så selv om det er det Enger du vil koordinere tilgang til blant flere tråder, koordinerer Java's mekanisme for å gjøre det faktisk tilgang til kode. Dette betyr at bare hvis du gjør dataene private, vil du kunne kontrollere tilgangen til dataene ved å kontrollere tilgangen til koden som manipulerer dataene.

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