Vejledning til det synkroniserede nøgleord i Java

1. Oversigt

Denne hurtige artikel vil være en introduktion til brugen af synkroniseret blokere i Java.

Kort sagt, i et miljø med flere tråde, opstår der en løbetilstand, når to eller flere tråde forsøger at opdatere mutable delte data på samme tid. Java tilbyder en mekanisme til at undgå race betingelser ved at synkronisere trådadgang til delte data.

Et stykke logik markeret med synkroniseret bliver en synkroniseret blok, tillader kun en tråd at udføre på et givet tidspunkt.

2. Hvorfor synkronisering?

Lad os overveje en typisk race-tilstand, hvor vi beregner summen, og flere tråde udfører Beregn() metode:

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

Og lad os skrive en simpel test:

@Test offentlig ugyldighed 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 bruger simpelthen en ExecutorService med en 3-trådspool til at udføre Beregn() 1000 gange.

Hvis vi udfører dette serielt, ville den forventede output være 1000, men vores udførelse med flere tråde mislykkes næsten hver gang med en inkonsekvent faktisk produktion f.eks .:

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

Dette resultat er selvfølgelig ikke uventet.

En enkel måde at undgå løbetilstanden er at gøre operationen trådsikker ved hjælp af synkroniseret nøgleord.

3. Den Synkroniseret Nøgleord

Det synkroniseret nøgleord kan bruges på forskellige niveauer:

  • Instansmetoder
  • Statiske metoder
  • Kodeblokke

Når vi bruger en synkroniseret blok, internt bruger Java en skærm, også kendt som skærmlås eller iboende lås, til at give synkronisering. Disse skærme er bundet til et objekt, så alle synkroniserede blokke af det samme objekt kan kun have en tråd, der udfører dem på samme tid.

3.1. Synkroniseret Instansmetoder

Du skal blot tilføje synkroniseret nøgleord i metodedeklarationen for at gøre metoden synkroniseret:

offentlig synkroniseret tomrum synkroniseretCalculate () {setSum (getSum () + 1); }

Bemærk, at når vi først har synkroniseret metoden, passerer testsagen med den faktiske output som 1000:

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

Instansmetoder er synkroniseret over forekomsten af ​​klassen, der ejer metoden. Hvilket betyder, at kun en tråd pr. Forekomst af klassen kan udføre denne metode.

3.2. Synkroniseret Static Metoder

Statiske metoder er synkroniseret ligesom instansmetoder:

 offentlig statisk synkroniseret tomrum syncStaticCalculate () {staticSum = staticSum + 1; }

Disse metoder er synkroniseret på den Klasse objekt tilknyttet klassen og da kun en Klasse objekt findes pr. JVM pr. klasse, kun en tråd kan udføres inde i en statisk synkroniseret metode pr. klasse, uanset hvor mange forekomster den har.

Lad os teste det:

@ Test offentlig ugyldighed 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. Synkroniseret Blokke inden for metoder

Nogle gange ønsker vi ikke at synkronisere hele metoden, men kun nogle instruktioner i den. Dette kan opnås ved ansøger synkroniseret til en blok:

offentlig ugyldighed performSynchronisedTask () {synkroniseret (dette) {setCount (getCount () + 1); }}

Lad os teste ændringen:

@Test offentlig ugyldighed 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 ()); }

Bemærk, at vi har bestået en parameter det her til synkroniseret blok. Dette er monitorobjektet, koden inde i blokken synkroniseres på monitorobjektet. Kort sagt, kun en tråd pr. Monitorobjekt kan udføres inde i den kodeblok.

Hvis metoden er statisk, ville vi videregive klassens navn i stedet for objektreferencen. Og klassen ville være en skærm til synkronisering af blokken:

offentlig statisk ugyldighed performStaticSyncTask () {synkroniseret (SynchronisedBlocks.class) {setStaticCount (getStaticCount () + 1); }}

Lad os teste blokken inde i statisk metode:

@Test offentlig ugyldighed 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 bag synkroniseret metoder og blokke er genindflydende. Det vil sige, den aktuelle tråd kan erhverve det samme synkroniseret lås igen og igen, mens du holder den:

Objektlås = nyt objekt (); synkroniseret (lås) {System.out.println ("Første gang du erhverver det"); synkroniseret (lås) {System.out.println ("Indtastning igen"); synkroniseret (lås) {System.out.println ("Og igen"); }}}

Som vist ovenfor, mens vi er i en synkroniseret blokere, kan vi gentage den samme skærmlås gentagne gange.

4. Konklusion

I denne hurtige artikel har vi set forskellige måder at bruge synkroniseret nøgleord for at opnå trådsynkronisering.

Vi undersøgte også, hvordan en race-tilstand kan påvirke vores applikation, og hvordan synkronisering hjælper os med at undgå det. For mere om trådsikkerhed ved hjælp af låse i Java henvises til vores java.util.concurrent.Locks artikel.

Den komplette kode til denne vejledning er tilgængelig på GitHub.