Inleiding tot synchronisatie in Java

Synchronisatie is een Java-functie die verhindert dat meerdere threads tegelijkertijd toegang proberen te krijgen tot de gemeenschappelijk gedeelde bronnen. Hier verwijzen gedeelde bronnen naar externe bestandsinhoud, klassenvariabelen of database-records.

Synchronisatie wordt veel gebruikt in multithread-programmeren. "Gesynchroniseerd" is het sleutelwoord dat uw code de mogelijkheid biedt om slechts één thread daarop te laten werken zonder interferentie van een andere thread gedurende die periode.

Waarom hebben we synchronisatie in Java nodig?

  • Java is een multithreaded programmeertaal. Dit betekent dat twee of meer threads tegelijkertijd kunnen worden uitgevoerd om een ​​taak te voltooien. Wanneer threads gelijktijdig worden uitgevoerd, is er een grote kans dat zich een scenario voordoet waarbij uw code onverwachte resultaten kan opleveren.
  • Je vraagt ​​je misschien af ​​dat als multithreading foutieve uitvoer kan veroorzaken, waarom het dan als een belangrijke functie in Java wordt beschouwd?
  • Multithreading maakt uw code sneller door meerdere threads parallel te laten lopen, waardoor de uitvoeringstijd van uw codes wordt verkort en hoge prestaties worden geleverd. Het gebruik van de multithreading-omgeving leidt echter tot onnauwkeurige output vanwege een toestand die algemeen bekend staat als een race-toestand.

Wat is een raceconditie?

Wanneer twee of meer threads parallel worden uitgevoerd, hebben ze op dat moment de neiging om gedeelde bronnen te openen en te wijzigen. De sequenties waarin de threads worden uitgevoerd, worden bepaald door het thread scheduling-algoritme.

Hierdoor kan niet worden voorspeld in welke volgorde threads zullen worden uitgevoerd, aangezien dit uitsluitend door de threadplanner wordt bestuurd. Dit heeft invloed op de uitvoer van de code en resulteert in inconsistente uitvoer. Omdat meerdere threads met elkaar racen om de operatie te voltooien, wordt de toestand "race-conditie" genoemd.

Laten we bijvoorbeeld de onderstaande code bekijken:

Class Modify:
package JavaConcepts;
public class Modify implements Runnable(
private int myVar=0;
public int getMyVar() (
return myVar;
)
public void setMyVar(int myVar) (
this.myVar = myVar;
)
public void increment() (
myVar++;
)
@Override
public void run() (
// TODO Auto-generated method stub
this.increment();
System.out.println("Current thread being executed "+ Thread.currentThread().getName() + "Current Thread value " + this.getMyVar());
)
)
Class RaceCondition:
package JavaConcepts;
public class RaceCondition (
public static void main(String() args) (
Modify mObj = new Modify();
Thread t1 = new Thread(mObj, "thread 1");
Thread t2 = new Thread(mObj, "thread 2");
Thread t3 = new Thread(mObj, "thread 3");
t1.start();
t2.start();
t3.start();
)
)

Bij het opeenvolgend uitvoeren van de bovenstaande code zijn de uitgangen als volgt:

Ourput1:

Huidige draad die wordt uitgevoerd draad 1 Huidige draadwaarde 3

Huidige draad die wordt uitgevoerd draad 3 Huidige draadwaarde 2

Huidige draad die wordt uitgevoerd draad 2 Huidige draadwaarde 3

Output2:

Huidige draad die wordt uitgevoerd draad 3 Huidige draadwaarde 3

Huidige draad die wordt uitgevoerd draad 2 Huidige draadwaarde 3

Huidige draad die wordt uitgevoerd draad 1 Huidige draadwaarde 3

OUTPUT3:

Huidige draad die wordt uitgevoerd draad 2 Huidige draadwaarde 3

Huidige draad die wordt uitgevoerd draad 1 Huidige draadwaarde 3

Huidige draad die wordt uitgevoerd draad 3 Huidige draadwaarde 3

Output4:

Huidige draad die wordt uitgevoerd draad 1 Huidige draadwaarde 2

Huidige draad die wordt uitgevoerd draad 3 Huidige draadwaarde 3

Huidige draad die wordt uitgevoerd draad 2 Huidige draadwaarde 2

  • Uit het bovenstaande voorbeeld kunt u concluderen dat de threads willekeurig worden uitgevoerd en dat de waarde onjuist is. Volgens onze logica moet de waarde met 1 worden verhoogd. Echter, hier is de uitvoerwaarde in de meeste gevallen 3 en in enkele gevallen is het 2.
  • Hier is de variabele "myVar" de gedeelde bron waarop meerdere threads worden uitgevoerd. De threads openen en wijzigen tegelijkertijd de waarde van "myVar". Laten we eens kijken wat er gebeurt als we de andere twee threads becommentariëren.

De output is in dit geval:

De huidige thread die wordt uitgevoerd thread 1 Huidige threadwaarde 1

Dit betekent dat wanneer een enkele thread wordt uitgevoerd, de uitvoer is zoals verwacht. Wanneer echter meerdere threads worden uitgevoerd, wordt de waarde door elke thread gewijzigd. Daarom moet men het aantal threads dat aan een gedeelde bron werkt tot één thread per keer beperken. Dit wordt bereikt met behulp van synchronisatie.

Wat is synchronisatie in Java?

  • Synchronisatie in Java wordt bereikt met behulp van het trefwoord "gesynchroniseerd". Dit trefwoord kan worden gebruikt voor methoden of blokken of objecten, maar kan niet worden gebruikt met klassen en variabelen. Een gesynchroniseerd stuk code geeft slechts toegang tot één thread op een bepaald moment.
  • Een gesynchroniseerd stuk code heeft echter invloed op de prestaties van de code omdat het de wachttijd verhoogt van andere threads die toegang proberen te krijgen. Een stukje code moet dus alleen worden gesynchroniseerd als er een kans is dat er een raceconditie optreedt. Zo niet, dan moet je het vermijden.

Hoe werkt synchronisatie in Java intern?

  • Interne synchronisatie in Java is geïmplementeerd met behulp van lock (ook bekend als een monitor) concept. Elk Java-object heeft zijn eigen slot. In een gesynchroniseerd codeblok moet een thread het slot verkrijgen voordat hij dat specifieke codeblok kan uitvoeren. Zodra een thread het slot heeft verkregen, kan het dat stuk code uitvoeren.
  • Na voltooiing van de uitvoering wordt het slot automatisch vrijgegeven. Als een andere thread moet werken op de gesynchroniseerde code, wacht deze tot de huidige thread die erop werkt het slot ontgrendelt. Dit proces van het verwerven en vrijgeven van sloten wordt intern verzorgd door de Java virtuele machine. Een programma is niet verantwoordelijk voor het verkrijgen en vrijgeven van sloten door de thread. De resterende threads kunnen echter elk ander niet-gesynchroniseerd stuk code tegelijkertijd uitvoeren.

Laten we ons vorige voorbeeld synchroniseren door de code in de run-methode te synchroniseren met behulp van het gesynchroniseerde blok in de klasse "Wijzigen" zoals hieronder:

Class Modify:
package JavaConcepts;
public class Modify implements Runnable(
private int myVar=0;
public int getMyVar() (
return myVar;
)
public void setMyVar(int myVar) (
this.myVar = myVar;
)
public void increment() (
myVar++;
)
@Override
public void run() (
// TODO Auto-generated method stub
synchronized(this) (
this.increment();
System.out.println("Current thread being executed "
+ Thread.currentThread().getName() + " Current Thread value " + this.getMyVar());
)
)
)

De code voor de klasse "RaceCondition" blijft hetzelfde. Bij het uitvoeren van de code is de uitvoer als volgt:

Output1:

De huidige thread die wordt uitgevoerd thread 1 Huidige threadwaarde 1

De huidige thread die wordt uitgevoerd thread 2 Huidige threadwaarde 2

De huidige thread die wordt uitgevoerd thread 3 Huidige threadwaarde 3

Output2:

De huidige thread die wordt uitgevoerd thread 1 Huidige threadwaarde 1

De huidige thread die wordt uitgevoerd thread 3 Huidige threadwaarde 2

De huidige thread die wordt uitgevoerd thread 2 Huidige threadwaarde 3

Merk op dat onze code de verwachte uitvoer levert. Hier verhoogt elke thread de waarde met 1 voor de variabele "myVar" (in de klasse "Modify").

Opmerking: synchronisatie is vereist wanneer meerdere threads op hetzelfde object werken. Als meerdere threads op meerdere objecten werken, is synchronisatie niet vereist.

Laten we bijvoorbeeld de code in de klasse “RaceCondition” zoals hieronder wijzigen en met de eerder niet-gesynchroniseerde klasse “Wijzigen” werken.

package JavaConcepts;
public class RaceCondition (
public static void main(String() args) (
Modify mObj = new Modify();
Modify mObj1 = new Modify();
Modify mObj2 = new Modify();
Thread t1 = new Thread(mObj, "thread 1");
Thread t2 = new Thread(mObj1, "thread 2");
Thread t3 = new Thread(mObj2, "thread 3");
t1.start();
t2.start();
t3.start();
)
)

Output:

De huidige thread die wordt uitgevoerd thread 1 Huidige threadwaarde 1

De huidige thread die wordt uitgevoerd thread 2 Huidige threadwaarde 1

De huidige thread die wordt uitgevoerd thread 3 Huidige threadwaarde 1

Typen synchronisatie in Java:

Er zijn twee soorten thread-synchronisatie: de ene is wederzijds exclusief en de andere inter-thread-communicatie.

1. vrijwel exclusief

  • Gesynchroniseerde methode.
  • Statische gesynchroniseerde methode
  • Gesynchroniseerd blok.

2. Draadcoördinatie (inter-thread communicatie in Java)

Wederzijds exclusief:

  • In dit geval verkrijgen threads het slot voordat ze aan een object werken, waardoor wordt voorkomen dat wordt gewerkt met objecten waarvan de waarden door andere threads zijn gemanipuleerd.
  • Dit kan op drie manieren worden bereikt:

ik. Gesynchroniseerde methode: we kunnen het "gesynchroniseerde" trefwoord gebruiken voor een methode, waardoor het een gesynchroniseerde methode wordt. Elke thread die de gesynchroniseerde methode aanroept, krijgt de vergrendeling voor dat object en geeft deze vrij zodra de bewerking is voltooid. In het bovenstaande voorbeeld kunnen we onze methode "run ()" gesynchroniseerd maken met behulp van het trefwoord "synchroon" na de toegangsmodificator.

@Override
public synchronized void run() (
// TODO Auto-generated method stub
this.increment();
System.out.println("Current thread being executed "
+ Thread.currentThread().getName() + " Current Thread value " + this.getMyVar());
)

De output voor deze case is:

De huidige thread die wordt uitgevoerd thread 1 Huidige threadwaarde 1

De huidige thread die wordt uitgevoerd thread 3 Huidige threadwaarde 2

De huidige thread die wordt uitgevoerd thread 2 Huidige threadwaarde 3

ii. Statische gesynchroniseerde methode: om statische methoden te synchroniseren, moet u de vergrendeling op klassenniveau verkrijgen. Nadat een thread alleen de vergrendeling op klassenniveau heeft verkregen, kan deze een statische methode uitvoeren. Hoewel een thread de klasse niveau lock bevat, kan geen andere thread een andere statische gesynchroniseerde methode van die klasse uitvoeren. De andere threads kunnen echter elke andere reguliere methode of reguliere statische methode of zelfs niet-statische gesynchroniseerde methode van die klasse uitvoeren.

Laten we bijvoorbeeld onze klasse "Wijzigen" overwegen en deze wijzigen door onze methode "increment" te converteren naar een statische gesynchroniseerde methode. De codewijzigingen zijn als volgt:

package JavaConcepts;
public class Modify implements Runnable(
private static int myVar=0;
public int getMyVar() (
return myVar;
)
public void setMyVar(int myVar) (
this.myVar = myVar;
)
public static synchronized void increment() (
myVar++;
System.out.println("Current thread being executed " + Thread.currentThread().getName() + " Current Thread value " + myVar);
)
@Override
public void run() (
// TODO Auto-generated method stub
increment();
)
)

iii. Gesynchroniseerd blok: een van de belangrijkste nadelen van de gesynchroniseerde methode is dat het de wachttijd voor threads verhoogt, wat invloed heeft op de prestaties van de code. Daarom moet men, om alleen de vereiste coderegels te kunnen synchroniseren in plaats van de hele methode, gebruik maken van een gesynchroniseerd blok. Het gebruik van gesynchroniseerd blok vermindert de wachttijd van de threads en verbetert ook de prestaties. In het vorige voorbeeld hebben we al gebruik gemaakt van gesynchroniseerd blok terwijl we onze code voor het eerst synchroniseerden.

Voorbeeld:
public void run() (
// TODO Auto-generated method stub
synchronized(this) (
this.increment();
System.out.println("Current thread being executed "
+ Thread.currentThread().getName() + " Current Thread value " + this.getMyVar());
)
)

Discussie coördinatie:

Voor gesynchroniseerde threads is communicatie tussen threads een belangrijke taak. Ingebouwde methoden die helpen bij het bereiken van inter-thread-communicatie voor gesynchroniseerde code zijn namelijk:

  • wacht()
  • de hoogte ()
  • notifyAll ()

Opmerking: deze methoden behoren tot de objectklasse en niet tot de threadklasse. Als een thread deze methoden op een object kan aanroepen, moet deze de vergrendeling van dat object vasthouden. Deze methoden zorgen er ook voor dat een thread zijn vergrendeling loslaat op het object waarop deze wordt aangeroepen.

wait (): een thread bij het aanroepen van de methode wait (), geeft de vergrendeling van het object vrij en gaat in de wachtstand. Het heeft twee methode-overbelastingen:

  • publieke finale ongeldig wait () gooit InterruptedException
  • openbare finale ongeldige wachttijd (lange time-out) gooit InterruptedException
  • openbare finale ongeldige wachttijd (lange time-out, int nanos) gooit InterruptedException

kennis (): een thread verzendt een signaal naar een andere thread in de wachtstand door gebruik te maken van de methode kennis (). Het stuurt de melding naar slechts één thread zodat deze thread de uitvoering kan hervatten. Welke thread de melding tussen alle threads in de wachtende status ontvangt, is afhankelijk van de Java Virtual Machine.

  • openbare definitieve ongeldige kennisgeving ()

kennisAll (): wanneer een thread de methode kennisAll () aanroept, wordt elke thread in de wachttoestand op de hoogte gebracht. Deze threads worden de een na de ander uitgevoerd op basis van de volgorde bepaald door de Java Virtual Machine.

  • openbare laatste ongeldige kennisgevingAll ()

Conclusie

In dit artikel hebben we gezien hoe werken in een multi-threaded omgeving kan leiden tot inconsistentie van gegevens vanwege een race-situatie. Hoe synchronisatie ons helpt dit te overwinnen door een enkele thread te beperken om tegelijkertijd op een gedeelde bron te werken. Ook hoe gesynchroniseerde threads met elkaar communiceren.

Aanbevolen artikelen:

Dit is een handleiding geweest voor Wat is synchronisatie in Java ?. Hier bespreken we de introductie, het begrip, de behoefte, de werking en de soorten synchronisatie met enkele voorbeeldcode. U kunt ook onze andere voorgestelde artikelen doornemen voor meer informatie -

  1. Serialisatie in Java
  2. Wat is Generics in Java?
  3. Wat is API in Java?
  4. Wat is een binaire boom in Java?
  5. Voorbeelden en hoe generieken werken in C #