Bevezetés a Java szinkronizálásba

A szinkronizálás egy Java szolgáltatás, amely korlátozza a több szál számára a megosztott erőforrások egyszerre történő elérésének megkísérlését. Itt a megosztott erőforrások a külső fájltartalmakra, osztályváltozókra vagy adatbázisrekordokra vonatkoznak.

A szinkronizálást széles körben használják a többszálú programozásban. A „szinkronizált” kulcsszó, amely biztosítja a kódjának azt a képességét, hogy csak egyetlen szál működhessen rajta, anélkül, hogy bármilyen más szál zavarná azt az időszakot.

Miért van szükségünk szinkronizálásra Java-ban?

  • A Java egy többszálú programozási nyelv. Ez azt jelenti, hogy két vagy több szál egyidejűleg futhat egy feladat teljesítése felé. Amikor a szálak egyszerre futnak, nagy a esélye annak, hogy olyan forgatókönyv forduljon elő, ahol a kód váratlan eredményeket eredményezhet.
  • Kíváncsi lehet, hogy ha a többszálak hibás kimeneteket okozhatnak, akkor miért tartják fontos funkciónak a Java-ban?
  • A többszálú verzió gyorsabbá teszi a kódot, ha több szálat futtat párhuzamosan, ezáltal csökkentve a kódok végrehajtási idejét, és nagy teljesítményt biztosítva. A többszálú környezet használata azonban pontatlan outputokhoz vezet egy olyan körülmény miatt, amelyet általában versenyfeltételként hívnak.

Mi a verseny feltétele?

Ha két vagy több szál párhuzamosan fut, akkor hajlamosak a megosztott erőforrások elérésére és módosítására az adott időpontban. A szálakat, amelyekben a szálak végrehajtásra kerülnek, a szálak ütemezési algoritmus határozza meg.

Ezért nem lehet megjósolni a szálak végrehajtásának sorrendjét, mivel ezt kizárólag a szálak ütemezője irányítja. Ez befolyásolja a kód kimenetét, és következetlen kimeneteket eredményez. Mivel a szálak egymással versenyeznek a művelet befejezéséhez, ezt az állapotot „versenyfeltételnek” nevezik.

Például vegyük figyelembe az alábbi kódot:

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();
)
)

A fenti kód egymást követő futtatásakor a kimenetek a következők:

Ourput1:

Aktuális szál végrehajtása 1. szál Aktuális szál értéke 3

Aktuális szál végrehajtása 3. szál Aktuális szál értéke 2

Aktuális szál végrehajtása 2. szál Aktuális szál értéke 3

Output2:

Aktuális szál végrehajtása 3. szál Aktuális szál értéke 3

Aktuális szál végrehajtása 2. szál Aktuális szál értéke 3

Aktuális szál végrehajtása 1. szál Aktuális szál értéke 3

Output3:

Aktuális szál végrehajtása 2. szál Aktuális szál értéke 3

Aktuális szál végrehajtása 1. szál Aktuális szál értéke 3

Aktuális szál végrehajtása 3. szál Aktuális szál értéke 3

Output4:

Aktuális szál végrehajtása 1. szál Aktuális szál értéke 2

Aktuális szál végrehajtása 3. szál Aktuális szál értéke 3

Aktuális szál végrehajtása 2. szál Aktuális szál értéke 2

  • A fenti példából arra következtethetünk, hogy a szálakat véletlenszerűen hajtják végre, és az érték is helytelen. A logikánk szerint az értéket 1-gyel kell növelni. Ebben az esetben a kimeneti érték a legtöbb esetben 3, néhány esetben pedig 2.
  • Itt a “myVar” változó a megosztott erőforrás, amelyen több szál fut. A szálak egyszerre férnek hozzá és módosítják a „myVar” értékét. Nézzük meg, mi történik, ha kommentáljuk a másik két szálat.

A kimenet ebben az esetben:

Az aktuális szál végrehajtása 1. szál Aktuális szál értéke 1.

Ez azt jelenti, hogy amikor egy szál fut, a kimenet a vártnak felel meg. Ha azonban több szál fut, az értéket az egyes szálak módosítják. Ezért a megosztott erőforráson dolgozó szálak számát egyszerre egyetlen szálra kell korlátozni. Ezt szinkronizációval érjük el.

Megértés, mi a szinkronizálás a Java-ban

  • A szinkronizálás a Java-ban a „szinkronizált” kulcsszó segítségével érhető el. Ez a kulcsszó használható módszerekhez, blokkokhoz vagy objektumokhoz, de nem használható osztályokkal és változókkal. A szinkronizált kóddarab csak egy szálhoz fér hozzá és módosíthatja azt egy adott időben.
  • A szinkronizált kóddarab azonban befolyásolja a kód teljesítményét, mivel megnöveli a szálat elérni próbáló szálak várakozási idejét. Tehát egy kóddarabot csak akkor kell szinkronizálni, ha van esély a versenyfolyamatok kialakulására. Ha nem, akkor ne kerülje el.

Hogyan működik a belső szinkronizálás a Java-ban?

  • A Java belső szinkronizálása a lock (monitorként is ismert) koncepció segítségével valósult meg. Minden Java objektumnak van saját zárja. A szinkronizált kódblokkban a szálnak meg kell szereznie a zárolást, mielőtt végrehajthatja az adott kódblokkot. Miután egy szál megszerezte a zárolást, végrehajthatja azt a kóddarabot.
  • A végrehajtás befejezése után automatikusan elengedi a zárat. Ha egy másik szálaknak működniük kell a szinkronizált kódon, akkor megvárja, hogy az aktuális szál működtesse a zárat. A zárak beszerzésének és elengedésének ezen folyamatát a Java virtuális gép gondoskodik. A program nem felelős a zárak megszerzéséért és felszabadításáért a szál segítségével. A fennmaradó szálak azonban bármilyen más nem szinkronizált kóddarabot is végrehajthatnak egyszerre.

Szinkronizáljuk az előző példát úgy, hogy a futtatás módszerén belüli kódot szinkronizáljuk a „Modifikálás” osztályba tartozó szinkronizált blokk segítségével az alábbiak szerint:

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());
)
)
)

A „RaceCondition” osztály kódja változatlan. A kód futtatásakor a kimenet a következő:

Output1:

Az aktuális szál végrehajtása 1. szál Aktuális szál értéke 1.

Az aktuális szál végrehajtása 2. szál Aktuális szál értéke 2

Az aktuális szál végrehajtása 3. szál Aktuális szál értéke 3

Output2:

Az aktuális szál végrehajtása 1. szál Aktuális szál értéke 1.

Az aktuális szál végrehajtása 3. szál Aktuális szál értéke 2

Az aktuális szál végrehajtása 2. szál Aktuális szál értéke 3

Vegye figyelembe, hogy kódunk biztosítja a várt outputot. Itt minden szál növeli az értéket 1-rel a „myVar” változónál (az „Modify” osztályban).

Megjegyzés: A szinkronizálás akkor szükséges, ha több szál működik ugyanazon az objektumon. Ha több szál több objektumon működik, akkor a szinkronizálás nem szükséges.

Például módosítsuk a kódot az alábbiak szerint a „RaceCondition” osztályban, és dolgozzunk az előzőleg nem szinkronizált „Modify” osztálytal.

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();
)
)

Kimenet:

Az aktuális szál végrehajtása 1. szál Aktuális szál értéke 1.

Az aktuális szál végrehajtása 2. szál Aktuális szál értéke 1

Az aktuális szál végrehajtása 3. szál Aktuális szál értéke 1

A szinkronizálás típusai Java-ban:

Kétféle szinkronizálás létezik: az egyik kölcsönösen kizárja a másik és a szálak közötti kommunikáció.

1.Kapcsolatban

  • Szinkronizált módszer.
  • Statikus szinkronizált módszer
  • Szinkronizált blokk.

2.Fér koordináció (szálak közötti kommunikáció java-ban)

Egymást kizáró:

  • Ebben az esetben a szálak megkapják a reteszt, mielőtt egy tárgyon működnének, elkerülve ezzel az olyan tárgyakkal való munkát, amelyek értékét más szálak manipulálták.
  • Ezt három módon lehet elérni:

én. Szinkronizált módszer: Használhatjuk a „szinkronizált” kulcsszót egy módszerhez, így szinkronizált módszerré válhatunk. Minden szálat, amely a szinkronizált módszert idézi elő, megkapja az objektum zárját, és a művelet befejezése után engedi fel. A fenti példában a „run ()” módszert szinkronizálva állíthatjuk elő, a hozzáférés módosítóját követő „szinkronizált” kulcsszó használatával.

@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());
)

Ebben az esetben a kimenet lesz:

Az aktuális szál végrehajtása 1. szál Aktuális szál értéke 1.

Az aktuális szál végrehajtása 3. szál Aktuális szál értéke 2

Az aktuális szál végrehajtása 2. szál Aktuális szál értéke 3

ii. Statikus szinkronizált módszer: A statikus módszerek szinkronizálásához meg kell szerezni az osztályszintű zárolást. Miután egy szál csak akkor kap osztályszintű zárolást, akkor képes lesz statikus módszert végrehajtani. Miközben egy szál tartja az osztályszintű zárolást, egyetlen szál sem tudja végrehajtani az osztály többi statikus szinkronizált módszerét. A többi szál azonban végrehajthat az osztály bármely más szokásos módszerét vagy szokásos statikus módszerét, vagy akár nem statikus szinkronizált módszerét.

Például mérlegeljük a „Modify” osztályunkat, és végezzünk változtatásokat abban, ha a „növekményes” módszert statikus szinkronizált módszerre konvertáljuk. A kód megváltozása az alábbiak szerint történik:

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. Szinkronizált blokk: A szinkronizált módszer egyik fő hátránya, hogy növeli a szálak várakozási idejét, és ez befolyásolja a kód teljesítményét. Ezért ahhoz, hogy a teljes módszer helyett csak a szükséges kódsorokat tudjuk szinkronizálni, szinkronizált blokkot kell használni. A szinkronizált blokk használata csökkenti a szálak várakozási idejét, és javítja a teljesítményt is. Az előző példában már használtuk a szinkronizált blokkot, miközben a kódot először szinkronizáltuk.

Példa:
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());
)
)

Menet koordináció:

A szinkronizált szálak esetében a szálak közötti kommunikáció fontos feladat. A szinkronizált kód szálak közötti kommunikációját elősegítő beépített módszerek a következők:

  • várjon()
  • értesíti ()
  • notifyAll ()

Megjegyzés: Ezek a módszerek az objektumosztályba tartoznak, és nem a szálak osztályába tartoznak. Ahhoz, hogy egy szál felhasználhassa ezeket a módszereket egy objektumon, az objektum zárját kell tartania. Ezenkívül ezek a módszerek a szálak elengedik a zárját azon az objektumon, amelyre hívják.

Várakozás (): A várakozás () módszer meghívására szolgáló szál elengedi az objektum zárját és várakozási állapotba kerül. Két módszerrel van túlterhelve:

  • nyilvános végleges érvénytelen várakozás () megszakítja az Exceptiont
  • a nyilvános végleges érvénytelen várakozás (hosszú időtúllépés) az InterruptedException programot dobja
  • a nyilvános végleges érvénytelen várakozás (hosszú időtúllépés, int nanos) InterruptedException-t dob

értesítés (): A szál jelet küld egy másik szálaknak várakozási állapotban az értesítés () módszer használatával. Az értesítést csak egy szálra küldi el, így ez a szál folytathatja a végrehajtását. A Java virtuális géptől függ, hogy melyik szál fogadja az értesítést az összes várakozó állapotú szál között.

  • nyilvános végleges érvénytelen értesítés ()

értesítésAll (): Amikor egy szál az értesítésAll () metódust hívja fel, a várakozási állapotban lévő minden szál értesítést kap. Ezeket a szálakat egymás után hajtják végre, a Java virtuális gép döntése alapján.

  • nyilvános végleges érvénytelen értesítésAll ()

Következtetés

Ebben a cikkben láttuk, hogy a többszálú környezetben történő munka hogyan vezethet az adatok következetlenségéhez a verseny körülményei miatt. Hogyan segíti a szinkronizálás ezt az áthidalást azáltal, hogy korlátozza az egyetlen szál működését egy megosztott erőforráson egy időben. A szinkronizált szálak hogyan kommunikálnak egymással.

Ajánlott cikkek:

Ez egy útmutató a Mi a szinkronizálás a Java-ban című részben? Itt tárgyaljuk a bevezetést, a megértést, a szükségletet, a munkát és a szinkronizálás típusait néhány mintakóddal. A további javasolt cikkeken keresztül további információkat is megtudhat -

  1. Szerializáció Java-ban
  2. Mi a Generics a Java-ban?
  3. Mi az API a Java-ban?
  4. Mi a bináris fa Java-ban?
  5. Példák és hogyan működnek a generikus gyógyszerek a C # -ben

Kategória: