Veiledning til det synkroniserte nøkkelordet i Java

1. Oversikt

Denne raske artikkelen vil være en introduksjon til bruk av synkronisert blokker i Java.

Enkelt sagt, i et miljø med flere tråder, oppstår en løpstilstand når to eller flere tråder prøver å oppdatere mutable delte data samtidig. Java tilbyr en mekanisme for å unngå løpsforhold ved å synkronisere trådtilgang til delt data.

Et stykke logikk merket med synkronisert blir en synkronisert blokk, slik at bare en tråd kan utføres til enhver tid.

2. Hvorfor synkronisering?

La oss vurdere en typisk løpstilstand der vi beregner summen og flere tråder utfører regne ut() metode:

offentlig klasse BaeldungSynchronizedMethods {private int sum = 0; offentlig tomrom beregne () {setSum (getSum () + 1); } // standard settere og getters} 

Og la oss skrive en enkel test:

@Test offentlig ugyldig givenMultiThread_whenNonSyncMethod () {ExecutorService service = Executors.newFixedThreadPool (3); BaeldungSynchronizedMethods summation = nye BaeldungSynchronizedMethods (); IntStream.range (0, 1000) .forEach (count -> service.submit (summation :: calc)); service.awaitTermination (1000, TimeUnit.MILLISECONDS); assertEquals (1000, summation.getSum ()); }

Vi bruker ganske enkelt en ExecutorService med et 3-tråders basseng for å utføre regne ut() 1000 ganger.

Hvis vi skulle utføre dette serielt, ville den forventede produksjonen være 1000, men vår utførelse med flere tråder mislykkes nesten hver gang med en inkonsekvent faktisk produksjon, for eksempel:

java.lang.AssertionError: forventet: men var: at org.junit.Assert.fail (Assert.java:88) at org.junit.Assert.failNotEquals (Assert.java:834) ...

Dette resultatet er selvfølgelig ikke uventet.

En enkel måte å unngå løpetilstanden på er å gjøre operasjonen trådsikker ved å bruke synkronisert nøkkelord.

3. Den Synkronisert Nøkkelord

De synkronisert nøkkelord kan brukes på forskjellige nivåer:

  • Instansmetoder
  • Statiske metoder
  • Kodeblokker

Når vi bruker en synkronisert blokkere, bruker Java internt en skjerm, også kjent som skjermlås eller egenlås, for å gi synkronisering. Disse skjermene er bundet til et objekt, og dermed kan alle synkroniserte blokker av det samme objektet bare ha en tråd som utfører dem samtidig.

3.1. Synkronisert Instansmetoder

Bare legg til synkronisert nøkkelord i metodedeklarasjonen for å gjøre metoden synkronisert:

offentlig synkronisert ugyldig synchronisedCalculate () {setSum (getSum () + 1); }

Legg merke til at når vi har synkronisert metoden, går testtilfellet, med faktisk utdata som 1000:

@Test offentlig ugyldighet givenMultiThread_whenMethodSync () {ExecutorService service = Executors.newFixedThreadPool (3); SynchronizedMethods method = new SynchronizedMethods (); IntStream.range (0, 1000) .forEach (count -> service.submit (metode :: synchronisedCalculate)); service.awaitTermination (1000, TimeUnit.MILLISECONDS); assertEquals (1000, method.getSum ()); }

Forekomst metoder er synkronisert over forekomsten av klassen som eier metoden. Hvilket betyr at bare en tråd per forekomst av klassen kan utføre denne metoden.

3.2. Synkronisert Static Metoder

Statiske metoder er synkronisert akkurat som eksempelmetoder:

 offentlig statisk synkronisert tomrom syncStaticCalculate () {staticSum = staticSum + 1; }

Disse metodene er synkronisert Klasse objekt assosiert med klassen og siden bare ett Klasse objekt eksisterer per JVM per klasse, bare en tråd kan utføres inne i statisk synkronisert metode per klasse, uavhengig av antall forekomster den har.

La oss teste det:

@Test offentlig ugyldig givenMultiThread_whenStaticSyncMethod () {ExecutorService service = Executors.newCachedThreadPool (); IntStream.range (0, 1000) .forEach (count -> service.submit (BaeldungSynchronizedMethods :: syncStaticCalculate)); service.awaitTermination (100, TimeUnit.MILLISECONDS); assertEquals (1000, BaeldungSynchronizedMethods.staticSum); }

3.3. Synkronisert Blokkerer innenfor metoder

Noen ganger ønsker vi ikke å synkronisere hele metoden, men bare noen instruksjoner i den. Dette kan oppnås ved søker synkronisert til en blokk:

offentlig ugyldig performSynchronisedTask () {synkronisert (dette) {setCount (getCount () + 1); }}

La oss teste endringen:

@Test offentlig ugyldighet givenMultiThread_whenBlockSync () {ExecutorService service = Executors.newFixedThreadPool (3); BaeldungSynchronizedBlocks synchronizedBlocks = nye BaeldungSynchronizedBlocks (); IntStream.range (0, 1000) .forEach (count -> service.submit (synchronizedBlocks :: performSynchronisedTask)); service.awaitTermination (100, TimeUnit.MILLISECONDS); assertEquals (1000, synchronizedBlocks.getCount ()); }

Legg merke til at vi passerte en parameter dette til synkronisert blokkere. Dette er skjermobjektet, koden inne i blokken blir synkronisert på skjermobjektet. Enkelt sagt, bare en tråd per monitorobjekt kan utføres inne i kodeblokken.

I tilfelle metoden er det statisk, ville vi passere klassenavnet i stedet for objektreferansen. Og klassen ville være en skjerm for synkronisering av blokken:

offentlig statisk ugyldighet performStaticSyncTask () {synkronisert (SynchronisedBlocks.class) {setStaticCount (getStaticCount () + 1); }}

La oss teste blokken inne i statisk metode:

@Test offentlig ugyldig givenMultiThread_whenStaticSyncBlock () {ExecutorService service = Executors.newCachedThreadPool (); IntStream.range (0, 1000) .forEach (count -> service.submit (BaeldungSynchronizedBlocks :: performStaticSyncTask)); service.awaitTermination (100, TimeUnit.MILLISECONDS); assertEquals (1000, BaeldungSynchronizedBlocks.getStaticCount ()); }

3.4. Reentrancy

Låsen bak synkronisert metoder og blokker er reentrant. Det vil si at den nåværende tråden kan tilegne seg det samme synkronisert lås igjen og igjen mens du holder den:

Objektlås = nytt objekt (); synkronisert (lås) {System.out.println ("Første gang du anskaffer det"); synkronisert (lås) {System.out.println ("Entering again"); synkronisert (lås) {System.out.println ("Og igjen"); }}}

Som vist ovenfor, mens vi er i en synkronisert blokkere, kan vi anskaffe den samme skjermlåsen gjentatte ganger.

4. Konklusjon

I denne raske artikkelen har vi sett forskjellige måter å bruke synkronisert nøkkelord for å oppnå trådsynkronisering.

Vi undersøkte også hvordan et løpstilstand kan påvirke applikasjonen vår, og hvordan synkronisering hjelper oss med å unngå det. For mer om trådsikkerhet ved bruk av låser i Java, se vår java.util.concurrent.Locks artikkel.

Den komplette koden for denne opplæringen er tilgjengelig på GitHub.


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