Guide till det synkroniserade nyckelordet i Java

1. Översikt

Den här snabba artikeln kommer att vara ett introduktion till att använda det synkroniserade blocket i Java.

Enkelt uttryckt, i en miljö med flera trådar, uppstår ett rasvillkor när två eller flera trådar försöker uppdatera muterbar delad data samtidigt. Java erbjuder en mekanism för att undvika rasförhållanden genom att synkronisera trådåtkomst till delad data.

En logik som är markerad med synkroniserad blir ett synkroniserat block som gör att endast en tråd kan köras vid varje given tidpunkt .

2. Varför synkronisering?

Låt oss överväga ett typiskt rasvillkor där vi beräknar summan och flera trådar utför beräkningsmetoden () :

public class BaeldungSynchronizedMethods { private int sum = 0; public void calculate() { setSum(getSum() + 1); } // standard setters and getters } 

Och låt oss skriva ett enkelt test:

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

Vi använder helt enkelt en ExecutorService med en 3-trådig pool för att utföra beräkningen () 1000 gånger.

Om vi ​​skulle utföra detta seriellt skulle den förväntade utgången vara 1000, men vårt flertrådiga utförande misslyckas nästan varje gång med en inkonsekvent faktisk utdata, t.ex.:

java.lang.AssertionError: expected: but was: at org.junit.Assert.fail(Assert.java:88) at org.junit.Assert.failNotEquals(Assert.java:834) ...

Detta resultat är naturligtvis inte oväntat.

Ett enkelt sätt att undvika tävlingsvillkor är att göra operationen trådsäker genom att använda det synkroniserade nyckelordet.

3. Det synkroniserade nyckelordet

Det synkroniserade nyckelordet kan användas på olika nivåer:

  • Instansmetoder
  • Statiska metoder
  • Kodblock

När vi använder ett synkroniserat block använder Java internt en bildskärm, även känd som skärmlås eller inneboende lås, för att tillhandahålla synkronisering. Dessa skärmar är bundna till ett objekt, så alla synkroniserade block av samma objekt kan bara ha en tråd som kör dem samtidigt.

3.1. Synkroniserade instansmetoder

Lägg bara till det synkroniserade nyckelordet i metoddeklarationen för att göra metoden synkroniserad:

public synchronized void synchronisedCalculate() { setSum(getSum() + 1); }

Lägg märke till att när vi har synkroniserat metoden passerar testfallet, med den faktiska utdata som 1000:

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

Instansmetoder synkroniseras över förekomsten av klassen som äger metoden. Vilket innebär att endast en tråd per instans av klassen kan utföra denna metod.

3.2. Synkroniserade Stati c Metoder

Statiska metoder synkroniseras precis som förekomstmetoder:

 public static synchronized void syncStaticCalculate() { staticSum = staticSum + 1; }

Dessa metoder är synkroniseradeklass objektet associerat med klass och eftersom endast en klass objektet finns per JVM per klass, kan endast en tråd exekvera inuti en statisk synkroniserad metod per klass, oberoende av det antal instanser den har.

Låt oss testa det:

@Test public void 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. Synkroniserade block inom metoder

Ibland vill vi inte synkronisera hela metoden utan bara några instruktioner i den. Detta kan uppnås genom att använda synkroniserat till ett block:

public void performSynchronisedTask() { synchronized (this) { setCount(getCount()+1); } }

Låt oss testa förändringen:

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

Lägg märke till att vi passerade en parameter detta till synkroniserade blocket. Detta är monitorobjektet, koden inuti blocket synkroniseras på monitorobjektet. Enkelt uttryckt, bara en tråd per monitorobjekt kan köras inuti det kodblocket.

Om metoden är statisk skickar vi klassnamnet istället för objektreferensen. Och klassen skulle vara en bildskärm för synkronisering av blocket:

public static void performStaticSyncTask(){ synchronized (SynchronisedBlocks.class) { setStaticCount(getStaticCount() + 1); } }

Låt oss testa blocket inuti den statiska metoden:

@Test public void 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. Återinträde

Låset bakom de synkroniserade metoderna och blocken återinträder. Det vill säga den nuvarande tråden kan få samma synkroniserade lås om och om igen medan du håller det:

Object lock = new Object(); synchronized (lock) { System.out.println("First time acquiring it"); synchronized (lock) { System.out.println("Entering again"); synchronized (lock) { System.out.println("And again"); } } }

Som visas ovan, medan vi befinner oss i ett synkroniserat block, kan vi få samma skärmlås upprepade gånger.

4. Slutsats

I den här snabba artikeln har vi sett olika sätt att använda det synkroniserade nyckelordet för att uppnå trådsynkronisering.

Vi undersökte också hur ett loppstillstånd kan påverka vår applikation och hur synkronisering hjälper oss att undvika det. Mer information om trådsäkerhet med hjälp av lås i Java finns i vår artikel om java.util.concurrent.Locks .

Den fullständiga koden för denna handledning finns tillgänglig på GitHub.