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:
Support til flere tråde er indbygget i Java-sprog og API
- 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
, g
og 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, int
s r
, g
og 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 RGBColor
s konstruktør og metoder. Konstruktionsdesignet og metoderne garanterer at:
RGBColor
s konstruktør giver altid variablerne korrekte startværdierMetoder
setColor ()
oginvertere ()
vil altid udføre gyldige tilstandstransformationer på disse variabler- 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
, g
og 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.
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åd | Udmelding | r | g | b | Farve |
ingen | objekt repræsenterer grønt | 0 | 255 | 0 | |
blå | blå tråd påkalder setColor (0, 0, 255) | 0 | 255 | 0 | |
blå | checkRGBVals (0, 0, 255); | 0 | 255 | 0 | |
blå | this.r = 0; | 0 | 255 | 0 | |
blå | this.g = 0; | 0 | 255 | 0 | |
blå | blå bliver forhindret | 0 | 0 | 0 | |
rød | rød tråd påkalder setColor (255, 0, 0) | 0 | 0 | 0 | |
rød | checkRGBVals (255, 0, 0); | 0 | 0 | 0 | |
rød | this.r = 255; | 0 | 0 | 0 | |
rød | this.g = 0; | 255 | 0 | 0 | |
rød | this.b = 0; | 255 | 0 | 0 | |
rød | rød tråd vender tilbage | 255 | 0 | 0 | |
blå | senere fortsætter den blå tråd | 255 | 0 | 0 | |
blå | this.b = 255 | 255 | 0 | 0 | |
blå | blå tråd vender tilbage | 255 | 0 | 255 | |
ingen | objekt repræsenterer magenta | 255 | 0 | 255 |
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:
Det er midlertidigt: Til sidst har den blå tråd til hensigt at indstille farven til blå.
- 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åd | Udmelding | r | g | b | Farve |
ingen | objekt repræsenterer grønt | 0 | 255 | 0 | |
blå | blå tråd påkalder setColor (0, 0, 255) | 0 | 255 | 0 | |
blå | checkRGBVals (0, 0, 255); | 0 | 255 | 0 | |
blå | this.r = 0; | 0 | 255 | 0 | |
blå | this.g = 0; | 0 | 255 | 0 | |
blå | blå bliver forhindret | 0 | 0 | 0 | |
rød | rød tråd påberåber sig getColor () | 0 | 0 | 0 | |
rød | int [] retVal = ny int [3]; | 0 | 0 | 0 | |
rød | retVal [0] = 0; | 0 | 0 | 0 | |
rød | retVal [1] = 0; | 0 | 0 | 0 | |
rød | retVal [2] = 0; | 0 | 0 | 0 | |
rød | returnere retVal; | 0 | 0 | 0 | |
rød | rød tråd returnerer sort | 0 | 0 | 0 | |
blå | senere fortsætter den blå tråd | 0 | 0 | 0 | |
blå | this.b = 255 | 0 | 0 | 0 | |
blå | blå tråd vender tilbage | 0 | 0 | 255 | |
ingen | objekt repræsenterer blå | 0 | 0 | 255 |
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:
- Synkroniser kritiske sektioner
- Gør det uforanderligt
- 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.