Gids voor het gesynchroniseerde trefwoord in Java

1. Overzicht

Dit korte artikel is een inleiding tot het gebruik van de gesynchroniseerd blok in Java.

Simpel gezegd, in een omgeving met meerdere threads treedt een raceconditie op wanneer twee of meer threads tegelijkertijd proberen veranderlijke gedeelde gegevens bij te werken. Java biedt een mechanisme om racecondities te vermijden door threadtoegang tot gedeelde gegevens te synchroniseren.

Een stukje logica gemarkeerd met gesynchroniseerd wordt een gesynchroniseerd blok, waardoor slechts één thread tegelijk kan worden uitgevoerd.

2. Waarom synchronisatie?

Laten we eens kijken naar een typische raceconditie waarbij we de som berekenen en meerdere threads de berekenen() methode:

openbare klasse BaeldungSynchronizedMethods {private int sum = 0; openbare leegte berekenen () {setSum (getSum () + 1); } // standaard setters en getters} 

En laten we een eenvoudige test schrijven:

@Test openbare ongeldige gegevenMultiThread_whenNonSyncMethod () {ExecutorService service = Executors.newFixedThreadPool (3); BaeldungSynchronizedMethods sommatie = nieuwe BaeldungSynchronizedMethods (); IntStream.range (0, 1000) .forEach (count -> service.submit (sommatie :: berekenen)); service.awaitTermination (1000, TimeUnit.MILLISECONDS); assertEquals (1000, summation.getSum ()); }

We gebruiken gewoon een ExecutorService met een pool van 3 threads om het berekenen() 1000 keer.

Als we dit serieel zouden uitvoeren, zou de verwachte output 1000 zijn, maar onze multi-threaded uitvoering mislukt bijna elke keer met een inconsistente werkelijke output, bijvoorbeeld:

java.lang.AssertionError: verwacht: maar was: op org.junit.Assert.fail (Assert.java:88) op org.junit.Assert.failNotEquals (Assert.java:834) ...

Dit resultaat is natuurlijk niet onverwacht.

Een eenvoudige manier om de raceconditie te vermijden, is door de bewerking draadveilig te maken door de gesynchroniseerd trefwoord.

3. Het Gesynchroniseerd Trefwoord

De gesynchroniseerd trefwoord kan op verschillende niveaus worden gebruikt:

  • Instantiemethoden
  • Statische methoden
  • Code blokken

Wanneer we een gesynchroniseerd block, intern gebruikt Java een monitor die ook wel monitorvergrendeling of intrinsieke vergrendeling wordt genoemd om synchronisatie mogelijk te maken. Deze monitoren zijn gebonden aan een object, dus alle gesynchroniseerde blokken van hetzelfde object kunnen slechts één thread hebben die ze tegelijkertijd uitvoert.

3.1. Gesynchroniseerd Instantiemethoden

Voeg gewoon het gesynchroniseerd trefwoord in de methode-declaratie om de methode te synchroniseren:

openbaar gesynchroniseerd ongeldig synchronisedCalculate () {setSum (getSum () + 1); }

Merk op dat zodra we de methode hebben gesynchroniseerd, de testcase slaagt, met de werkelijke output als 1000:

@Test openbare ongeldige gegevenMultiThread_whenMethodSync () {ExecutorService service = Executors.newFixedThreadPool (3); SynchronizedMethods-methode = nieuwe SynchronizedMethods (); IntStream.range (0, 1000) .forEach (count -> service.submit (methode :: synchronisedCalculate)); service.awaitTermination (1000, TimeUnit.MILLISECONDS); assertEquals (1000, method.getSum ()); }

Instantiemethoden zijn gesynchroniseerd over de instantie van de klasse die eigenaar is van de methode. Wat betekent dat slechts één thread per instantie van de klasse deze methode kan uitvoeren.

3.2. Gesynchroniseerd Static Methoden

Statische methoden zijn gesynchroniseerd net als instantiemethoden:

 openbare statische gesynchroniseerde ongeldige syncStaticCalculate () {staticSum = staticSum + 1; }

Deze methoden zijn gesynchroniseerd op de Klasse object geassocieerd met de klasse en sinds slechts één Klasse object bestaat per JVM per klasse, slechts één thread kan worden uitgevoerd binnen een statisch gesynchroniseerd methode per klasse, ongeacht het aantal instanties dat deze heeft.

Laten we het testen:

@Test openbare ongeldige gegevenMultiThread_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. Gesynchroniseerd Blokken binnen methoden

Soms willen we niet de hele methode synchroniseren, maar slechts enkele instructies erin. Dit kan worden bereikt door toepassen gesynchroniseerd met een blok:

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

Laten we de verandering testen:

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

Merk op dat we een parameter hebben doorgegeven dit naar de gesynchroniseerd blok. Dit is het monitorobject, de code in het blok wordt gesynchroniseerd op het monitorobject. Simpel gezegd, slechts één thread per monitorobject kan binnen dat codeblok worden uitgevoerd.

Voor het geval de methode is statisch, zouden we de klassenaam doorgeven in plaats van de objectreferentie. En de klasse zou een monitor zijn voor de synchronisatie van het blok:

openbare statische ongeldige performStaticSyncTask () {gesynchroniseerd (SynchronisedBlocks.class) {setStaticCount (getStaticCount () + 1); }}

Laten we het blok in het statisch methode:

@Test openbare ongeldige gegevenMultiThread_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. Herintreding

Het slot achter de gesynchroniseerd methodes en blokken is herintredend. Dat wil zeggen, de huidige thread kan hetzelfde verwerven gesynchroniseerd keer op keer vergrendelen terwijl u het vasthoudt:

Objectvergrendeling = nieuw object (); gesynchroniseerd (lock) {System.out.println ("Eerste keer verwerven"); gesynchroniseerd (vergrendelen) {System.out.println ("Opnieuw invoeren"); gesynchroniseerd (vergrendelen) {System.out.println ("En opnieuw"); }}}

Zoals hierboven getoond, terwijl we ons in een gesynchroniseerd blok, kunnen we dezelfde monitorvergrendeling herhaaldelijk verkrijgen.

4. Conclusie

In dit korte artikel hebben we verschillende manieren gezien om de gesynchroniseerd trefwoord om thread-synchronisatie te bereiken.

We hebben ook onderzocht hoe een raceconditie onze applicatie kan beïnvloeden, en hoe synchronisatie ons helpt dat te voorkomen. Voor meer informatie over draadveiligheid met behulp van vergrendelingen in Java raadpleegt u onze java.util.concurrent.Locks artikel.

De volledige code voor deze tutorial is beschikbaar op GitHub.


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