Programmering

JavaBeans: egenskaper, hendelser og trådsikkerhet

Java er et dynamisk språk som inkluderer brukervennlige multitrading språkkonstruksjoner og støtteklasser. Mange Java-programmer er avhengige av multitrading for å utnytte parallellitet i intern applikasjon, forbedre nettverksytelsen eller øke hastigheten på brukernes tilbakemeldinger. De fleste Java-kjøretider bruker multitrading for å implementere Java's søppelinnsamlingsfunksjon. Endelig er AWT også avhengig av separate tråder for å fungere. Kort sagt, selv de enkleste Java-programmene er født i et aktivt flertrådingsmiljø.

Java-bønner er derfor også distribuert i et så dynamisk, flertrådet miljø, og her ligger den klassiske faren for å møte løpsforhold. Løpsforholdene er tidsavhengige scenarier for programflyt som kan føre til korrupsjon (programdata). I det neste avsnittet vil jeg detaljere to slike scenarier. Hver Java-bønne må utformes med tanke på løpsforhold, slik at en bønne tåler samtidig bruk av flere klienttråder.

Flertrådingsproblemer med enkle egenskaper

Bønneimplementeringer må anta at flere tråder får tilgang til og / eller endrer en enkelt bønneinstans samtidig. Som et eksempel på en feil implementert bønne (når det gjelder multithreading bevissthet), bør du vurdere følgende BrokenProperties bønne og dens tilknyttede MTProperties testprogram:

BrokenProperties.java

importere java.awt.Point;

// Demo Bean som ikke beskytter mot bruk av flere tråder.

offentlig klasse BrokenProperties utvider punkt {

// ------------------------------------------------ ------------------- // set () / get () for 'Spot' eiendom // --------------- -------------------------------------------------- -

public void setSpot (Point point) {// 'spot' setter this.x = point.x; this.y = point.y;

} public Point getSpot () {// 'spot' getter returner dette; }} // Slutt på Bean / Class BrokenProperties

MTProperties.java

importere java.awt.Point; importverktøy. *; import verktøy.bønner. *;

offentlig klasse MTProperties utvider tråd {

beskyttede BrokenProperties myBean; // målet bønne å bash ..

beskyttet int myID; // hver tråd bærer litt ID

// ------------------------------------------------ ------------------- // hoved () inngangspunkt // ---------------------- --------------------------------------------- offentlig statisk ugyldig hoved ( Streng [] args) {

BrokenProperties bønne; Tråd tråd;

bønne = (BrokenProperties) BeansKit.newBean ("BrokenProperties");

for (int i = 0; i <20; i ++) {// start 20 tråder for å bash bean thread = new MTProperties (bean, i); // tråder får tilgang til bønnetråd.start (); }} // ------------------------------------------------------ --------------------- // MTProperties Constructor // ----------------------- --------------------------------------------

offentlige MTProperties (BrokenProperties bean, int id) {this.myBean = bean; // merk bønnen for å adressere dette. myID = id; // merk hvem vi er} // ----------------------------------------- -------------------------- // trådens hovedløkke: // gjør for alltid // opprett nytt tilfeldig punkt med x == y // fortell bønne å vedta Point som sin nye "spot" -egenskap // spør bean hva "spot" -egenskapen nå er satt til // kaste en wobbly hvis spot x ikke tilsvarer spot y // --------- -------------------------------------------------- -------- public void run () {int someInt; Punktpunkt = nytt punkt ();

while (true) {someInt = (int) (Math.random () * 100); point.x = someInt; point.y = noeInt; myBean.setSpot (punkt);

punkt = myBean.getSpot (); if (point.x! = point.y) {System.out.println ("Bønne ødelagt! x =" + point.x + ", y =" + point.y); System.exit (10); } System.out.print ((char) ('A' + myID)); System.out.flush (); }}} // Slutt på MTP-eiendommer for klasse

Merk: Verktøypakken importert av MTPegenskaper inneholder gjenbrukbare klasser og statiske metoder utviklet for boken av forfatteren.

De to kildekodelistene ovenfor definerer en bønne som heter BrokenProperties og klassen MTPegenskaper, som brukes til å trene bønnen innen 20 løpende tråder. La oss følge med MTPegenskaper' hoved() inngangspunkt: Først starter den en BrokenProperties bønne, etterfulgt av opprettelse og start av 20 tråder. Klasse MTPegenskaper strekker java.lang.Tråd, så alt vi trenger å gjøre for å bli klasse MTPegenskaper inn i en tråd er å overstyre klassen Tråds løpe() metode. Konstruktøren for trådene våre tar to argumenter: bønneobjektet tråden vil kommunisere med og en unik identifikasjon, som gjør det mulig å skille de 20 trådene enkelt i løpetid.

Virksomheten slutten av denne demo er vår løpe() metode i klassen MTPegenskaper. Her sløyfer vi for alltid, og skaper tilfeldige nye (x, y) punkter, men med følgende karakteristikk: deres x-koordinat er alltid lik y-koordinaten deres. Disse tilfeldige punktene overføres til bønnens setSpot () settermetoden og deretter umiddelbart lese tilbake ved hjelp av getSpot () getter-metoden. Du forventer lesningen få øye på for å være identisk med det tilfeldige punktet som ble opprettet for noen millisekunder siden. Her er et eksempel på programmet når det påkalles på kommandolinjen:

ABBBBBBBBBBBBBBBBBBDJJJJJJJJJJJJJJJJJJJJEGHHHHHHHHHHHHHHHHHHSSSSSSSSSSSSSS IICCBBBBBBBBBBBBBBBBBKBDLJMBOPLQNRTPHHHHHHHHFFFFFFFFFFFFSSSSSSSSSSSSSSSSSS FFFFFFFFFFFFFFFAAAAAACCCCCCCKKKKKKKKKKKKKKKKKMMMMMMMMMMMMMMMMMMMMMMMMMMMDD JEOQQQQQQQQQQQQQQQRRRRRRRRRRRRRRRRRBBBBBBBBBBBBBBBTTTTTTTTTTTTTTTTLPPPPPPP PPPPGGHHHFFFFFFFFIIIIIIIIIIIIIISSSSSSSSSSSSSSSSSSSSACCCCCCCCCCCCCCCCCCCKMD QQQQQQNNNNNNNNNNNNNNNNRRRRRTRRHHHHHHHHHFFFFFFFFFFFFFFFFFFIIIIIIIIIIIIIIIII MMMJEEEEEEEEEEDDDDEEEEEEEEEOOOOOOOOOOOOOOOOOOOOOOOOOOOQNNNNNNNNBTLPLRGFFFF FFFFFFFFFIIAAAAAAAAAAAAAAAAASSSSSSSSSSSSSSSSSSKKKKKKKKKKKKKKKKCCCCCCCMMJAA AACBean ødelagt! x = 67, y = 13 OOOOOOOOOOOOOOOOOOO 

Utgangen viser de 20 trådene som går parallelt (så langt som en menneskelig observatør går); hver tråd bruker ID-en den mottok ved konstruksjonstid for å skrive ut en av bokstavene EN til T, de første 20 bokstavene i alfabetet. Så snart en tråd oppdager at avlesningen få øye på egenskapen samsvarer ikke med den programmerte karakteristikken til x = y, tråden skriver ut en "Bean corrupted" -melding og stopper eksperimentet.

Det du ser er den statskorrupte bivirkningen av en rase-tilstand i bønnens setSpot () kode. Her er den metoden igjen:

public void setSpot (Point point) {// 'spot' setter this.x = point.x; this.y = point.y; } 

Hva kan noen gang gå galt i en så enkel kode? Forestill deg tråd A ringer setSpot () med et poengargument lik (67,67). Hvis vi nå bremser klokken til Universet slik at vi kan se Java virtual machine (JVM) utføre hver Java-setning, en om gangen, kan vi forestille oss tråd A utføre x-koordinatkopi-setningen (this.x = punkt.x;) og så, plutselig, tråd A blir frossen av operativsystemet, og tråd C er planlagt å kjøre en stund. I sin forrige løpestatus, tråd C hadde nettopp opprettet sitt eget nye tilfeldige punkt (13,13), kalt setSpot () seg selv, og ble deretter frossen for å få plass til tråd M, rett etter at den hadde satt x-koordinaten til 13. Så, gjenopptok tråd C fortsetter nå med sin programmerte logikk: å sette y til 13 og sjekke om spot-egenskapen er lik (13, 13), men finner at den på mystisk vis har endret seg til en ulovlig tilstand av (67, 13); x-koordinaten er halve tilstanden til hva tråd A var innstillingen få øye på til, og y-koordinaten er halvparten av tilstanden til hva tråd Chadde sattfå øye på til. Sluttresultatet er at BrokenProperties bønne ender opp med en intern inkonsekvent tilstand: en ødelagt eiendom.

Når en ikke-atomisk datastruktur (det vil si en struktur som består av mer enn en del) kan modifiseres av mer enn en tråd om gangen, du må beskytte strukturen ved hjelp av en lås. I Java gjøres dette ved hjelp av synkronisert nøkkelord.

Advarsel: I motsetning til alle andre Java-typer, vær oppmerksom på at Java ikke garanterer det lang og dobbelt blir behandlet atomisk! Dette er fordi lang og dobbelt krever 64 bits, som er dobbelt så lang som de fleste moderne CPU-arkitekturenes ordlengde (32 bits). Både lasting og lagring av enkeltmaskinord er iboende atomoperasjoner, men å flytte 64-biters enheter krever to slike trekk, og disse er ikke beskyttet av Java av vanlig grunn: ytelse. (Noen CPUer tillater at systembussen låses for å utføre multiordoverføringer atomisk, men dette anlegget er ikke tilgjengelig på alle CPUer og vil i alle fall være utrolig dyrt å bruke på alle lang eller dobbelt manipulasjoner!) Så, selv når en eiendom består av bare en enkelt lang eller en singel dobbelt, bør du bruke fullstendige låseforholdsregler for å beskytte dine lengsler eller dobler fra å plutselig bli fullstendig ødelagt.

De synkronisert nøkkelord markerer en blokk med kode som et atomsteg. Koden kan ikke "deles", som når en annen tråd avbryter koden for potensielt å angi den blokkeringen på nytt (derav begrepet reentrant kode; all Java-kode skal være omskrevet). Løsningen for vår BrokenProperties bønne er triviell: bare bytt ut den setSpot () metode med dette:

public void setSpot (Point point) {// 'spot' setter synchronised (this) {this.x = point.x; this.y = point.y; }} 

Eller alternativt med dette:

offentlig synkronisert ugyldighet setSpot (Point point) {// 'spot' setter this.x = point.x; this.y = point.y; } 

Begge erstatningene er helt likeverdige, selv om jeg foretrekker den første stilen fordi den viser tydeligere hva den eksakte funksjonen til synkronisert nøkkelord er: en synkronisert blokk er alltid knyttet til et objekt som blir låst. Av låst Jeg mener at JVM først prøver å skaffe en lås (det vil si eksklusiv tilgang) på objektet (det vil si få eksklusiv tilgang til det), eller venter til objektet blir ulåst hvis det har blitt låst av en annen tråd. Låseprosessen garanterer at ethvert objekt bare kan låses (eller eies) av en tråd om gangen.

synkronisert (dette) syntaksen gjenspeiler tydelig den interne mekanismen: Argumentet i parentes er objektet som skal låses (det nåværende objektet) før kodeblokken angis. Den alternative syntaksen, der synkronisert nøkkelord brukes som en modifikator i en metodesignatur, er ganske enkelt en stenografisk versjon av den tidligere.

Advarsel: Når statiske metoder er merket synkronisert, det er ingen dette objekt å låse; bare forekomstmetoder er tilknyttet et gjeldende objekt. Så når klassemetoder er synkronisert, vil java.lang.Klasse objekt som tilsvarer metodens klasse brukes i stedet for å låse på. Denne tilnærmingen har alvorlige ytelsesimplikasjoner fordi en samling av klasseinstanser deler en enkelt tilknyttet Klasse gjenstand; når som helst Klasse objektet blir låst, er alle objekter i den klassen (enten 3, 50 eller 1000!) utestengt fra å påberope seg den samme statiske metoden. Med dette i bakhodet, bør du tenke deg om to ganger før du bruker synkronisering med statiske metoder.

I praksis må du alltid huske den eksplisitte synkroniserte formen fordi den lar deg "forstøve" den minste mulige kodeblokken innenfor en metode. Forkortelsesformen "atomiserer" hele metoden, som av ytelsesgrunner ofte er ikke hva vil du. Når en tråd har kommet inn i en atomblokk med kode, er det ingen annen tråd som må utføres noen synkronisert kode på samme objekt kan gjøre det.

Tips: Når en lås oppnås på en gjenstand, da alle synkronisert kode for det objektets klasse blir atomisk. Derfor, hvis klassen din inneholder mer enn én datastruktur som må behandles atomisk, men disse datastrukturene er ellers uavhengig av hverandre, så kan det oppstå en annen ytelsesflaskehals. Kunder som kaller synkroniserte metoder som manipulerer en intern datastruktur, vil blokkere alle andre klienter som kaller de andre metodene som håndterer andre atomdata-strukturer i klassen din. Det er klart at du bør unngå slike situasjoner ved å dele klassen i mindre klasser som bare håndterer en datastruktur som skal behandles atomisk om gangen.

JVM implementerer synkroniseringsfunksjonen ved å opprette køer av tråder som venter på at et objekt skal låses opp. Selv om denne strategien er god når det gjelder å beskytte konsistensen av sammensatte datastrukturer, kan den resultere i flertrådede trafikkork når en mindre enn effektiv kodeseksjon er merket som synkronisert.

Vær derfor alltid oppmerksom på hvor mye kode du synkroniserer: det bør være absolutt minimum. Tenk deg for eksempel vår setSpot () metoden opprinnelig besto av:

public void setSpot (Point point) {// 'spot' setter log.println ("setSpot () påkalt" + this.toString ()); this.x = punkt.x; this.y = point.y; } 

Selv om utskrift uttalelse kanskje logisk hører hjemme i setSpot () metode, er det ikke en del av utsagnssekvensen som trenger å grupperes i en atom helhet. Derfor, i dette tilfellet, den riktige måten å bruke synkronisert nøkkelordet vil være som følger:

public void setSpot (Point point) {// 'spot' setter log.println ("setSpot () påkalt" + this.toString ()); synkronisert (dette) {this.x = point.x; this.y = point.y; }} 

Den "late" måten og tilnærmingen du bør unngå, ser slik ut:

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