Introducere în sincronizare în Java

Sincronizarea este o caracteristică Java care restricționează mai multe fire de la încercarea de a accesa resursele comune partajate în același timp. Aici resursele partajate se referă la conținutul fișierului extern, la variabilele clasei sau la înregistrările bazei de date.

Sincronizarea este utilizată pe scară largă în programarea cu mai multe filete. „Sincronizat” este cuvântul cheie care oferă codului dvs. capacitatea de a permite doar unui singur fir să funcționeze asupra acestuia fără interferențe din orice alt thread în perioada respectivă.

De ce avem nevoie de Sincronizare în Java?

  • Java este un limbaj de programare multithreaded. Aceasta înseamnă că două sau mai multe fire pot rula simultan spre finalizarea unei sarcini. Când firele rulează simultan, există șanse mari să apară un scenariu în care codul dvs. poate oferi rezultate neașteptate.
  • V-ar putea să vă întrebați că dacă multitratarea poate produce rezultate eronate, atunci de ce este considerată o caracteristică importantă în Java?
  • Multithreading-ul vă face mai rapid codul rulând mai multe fire în paralel și reducând astfel timpul de execuție al codurilor și oferind performanțe ridicate. Cu toate acestea, folosirea mediului multitreading conduce la ieșiri inexacte din cauza unei condiții cunoscute în mod obișnuit ca o condiție de cursă.

Ce este o condiție de cursă?

Când două sau mai multe fire rulează în paralel, acestea tind să acceseze și să modifice resursele partajate la acel moment în timp. Secvențele în care thread-urile sunt executate sunt decise prin algoritmul de planificare a thread-urilor.

Datorită acestui lucru, nu se poate prezice ordinea în care vor fi executate thread-uri, deoarece este controlată exclusiv de planificatorul de fire. Aceasta afectează ieșirea codului și are ca rezultat ieșiri inconsecvente. Întrucât mai multe fire se întrec între ele pentru a finaliza operațiunea, starea este denumită „condiție de curse”.

De exemplu, să luăm în considerare codul de mai jos:

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

La rularea consecutivă a codului de mai sus, rezultatele vor fi următoarele:

Ourput1:

Firul curent care este executat thread 1 Valoarea curentă a firului 3

Firul curent care este executat thread 3 Valoarea curentă a firului 2

Firul curent care este executat thread 2 Valoarea curentă a firului 3

Output2:

Firul curent care este executat fir 3 Valoarea curentă a firului 3

Firul curent care este executat thread 2 Valoarea curentă a firului 3

Firul curent care este executat thread 1 Valoarea curentă a firului 3

Output3:

Firul curent care este executat thread 2 Valoarea curentă a firului 3

Firul curent care este executat thread 1 Valoarea curentă a firului 3

Firul curent care este executat fir 3 Valoarea curentă a firului 3

Output4:

Firul curent care este executat thread 1 Valoarea curentă a firului 2

Firul curent care este executat fir 3 Valoarea curentă a firului 3

Firul curent care este executat thread 2 Valoarea curentă a firului 2

  • Din exemplul de mai sus, puteți concluziona că firele sunt executate la întâmplare și, de asemenea, valoarea este incorectă. În conformitate cu logica noastră, valoarea ar trebui să fie incrementată cu 1. Totuși, aici valoarea de ieșire în majoritatea cazurilor este 3 și în câteva cazuri, este de 2.
  • Aici variabila „myVar” este resursa partajată pe care se execută mai multe fire. Firele accesează și modifică simultan valoarea „myVar”. Să vedem ce se întâmplă dacă comentăm celelalte două fire.

Rezultatul în acest caz este:

Firul curent care este executat thread 1 Valoarea curentă a firului 1

Aceasta înseamnă că atunci când un singur fir rulează, ieșirea este așa cum este de așteptat. Cu toate acestea, atunci când se execută mai multe fire, valoarea este modificată de fiecare thread. Prin urmare, este necesar să restricționați numărul de fire care lucrează la o resursă partajată la un singur fir la un moment dat. Acest lucru este realizat folosind sincronizarea.

Înțelegerea a ceea ce este sincronizarea în Java

  • Sincronizarea în Java se realizează cu ajutorul cuvântului cheie „sincronizat”. Acest cuvânt cheie poate fi utilizat pentru metode sau blocuri sau obiecte, dar nu poate fi utilizat cu clase și variabile. O bucată de cod sincronizată permite accesului și modificarea unui singur fir la un moment dat.
  • Cu toate acestea, o bucată de cod sincronizată afectează performanța codului, deoarece crește timpul de așteptare al altor fire care încearcă să-l acceseze. Așadar, o bucată de cod ar trebui sincronizată numai atunci când există șansa ca o condiție de cursă să apară. Dacă nu, cineva ar trebui să o evite.

Cum funcționează intern Sincronizarea în Java?

  • Sincronizarea internă în Java a fost implementată cu ajutorul conceptului de blocare (cunoscut și sub numele de monitor). Fiecare obiect Java are propriul său blocaj. Într-un bloc de cod sincronizat, un thread trebuie să achiziționeze blocarea înainte de a putea executa acel anumit bloc de cod. Odată ce un thread achiziționează blocarea, poate executa acea bucată de cod.
  • La finalizarea execuției, eliberează automat blocarea. Dacă un alt thread necesită să funcționeze pe codul sincronizat, așteaptă ca firul curent care funcționează pe el să elibereze blocarea. Acest proces de achiziție și eliberare a blocărilor este îngrijit intern de mașina virtuală Java. Un program nu este responsabil pentru achiziționarea și eliberarea de blocări de fir. Firele rămase pot, totuși, să execute orice alt cod nesincronizat simultan.

Să sincronizăm exemplul nostru anterior prin sincronizarea codului în metoda rulării folosind blocul sincronizat din clasa „Modifica”, ca mai jos:

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

Codul clasei „RaceCondition” rămâne același. Acum la rularea codului, ieșirea este următoarea:

Output1:

Firul curent care este executat thread 1 Valoarea curentă a firului 1

Firul curent care este executat thread 2 Valoarea curentă a firului 2

Firul curent care este executat thread 3 Valoarea curentă a firului 3

Output2:

Firul curent care este executat thread 1 Valoarea curentă a firului 1

Firul curent care este executat thread 3 Valoarea curentă a firului 2

Firul curent care este executat thread 2 Valoarea curentă a firului 3

Observați că codul nostru oferă rezultatul așteptat. Aici fiecare thread crește valoarea cu 1 pentru variabila „myVar” (din clasa „Modify”).

Notă: Sincronizarea este necesară atunci când mai multe fire funcționează pe același obiect. Dacă mai multe fire funcționează pe mai multe obiecte, atunci nu este necesară sincronizarea.

De exemplu, să modificăm codul din clasa „RaceCondition” ca mai jos și să lucrăm cu clasa nesincronizată anterior „Modify”.

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

ieşire:

Firul curent care este executat thread 1 Valoarea curentă a firului 1

Firul curent care este executat fir 2 Valoarea curentă a firului 1

Firul curent care este executat fir 3 Valoarea curentă a firului 1

Tipuri de sincronizare în Java:

Există două tipuri de sincronizare a firelor, care se exclud reciproc, iar celălalt comunicare inter-thread.

1. Exclusiv

  • Metoda sincronizată.
  • Metoda sincronizată statică
  • Bloc sincronizat.

2. Coordonare de filet (comunicare inter-thread în java)

Exclusiv:

  • În acest caz, firele obțin blocarea înainte de a opera pe un obiect, evitând astfel să lucreze cu obiecte care și-au manipulat valorile prin alte fire.
  • Acest lucru poate fi realizat în trei moduri:

i. Metoda sincronizată: Putem folosi cuvântul cheie „sincronizat” pentru o metodă, devenind astfel o metodă sincronizată. Fiecare thread care invocă metoda sincronizată va obține blocarea pentru acel obiect și îl va elibera după ce operațiunea sa va fi finalizată. În exemplul de mai sus putem face ca metoda „run ()” să fie sincronizată folosind cuvântul cheie „sincronizat” după modificatorul de acces.

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

Rezultatul pentru acest caz va fi:

Firul curent care este executat thread 1 Valoarea curentă a firului 1

Firul curent care este executat thread 3 Valoarea curentă a firului 2

Firul curent care este executat thread 2 Valoarea curentă a firului 3

ii. Metoda sincronizată statică: Pentru a sincroniza metodele statice trebuie să dobândim blocarea nivelului clasei. După ce un thread obține blocarea nivelului clasei, atunci va putea să execute o metodă statică. În timp ce un thread deține blocarea nivelului clasei, niciun alt thread nu poate executa orice altă metodă statică sincronizată a clasei respective. Cu toate acestea, celelalte fire pot executa orice altă metodă regulată sau metodă statică regulată sau chiar o metodă sincronizată nestatică a clasei respective.

De exemplu, să luăm în considerare clasa noastră „Modificare” și să facem modificări prin transformarea metodei noastre „increment” la o metodă statică sincronizată. Modificările de cod sunt următoarele:

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. Bloc sincronizat: Unul dintre dezavantajele principale ale metodei sincronizate este faptul că crește timpul de așteptare a firelor afectând performanța codului. Prin urmare, pentru a putea sincroniza doar liniile de cod necesare în locul întregii metode, trebuie să folosiți un bloc sincronizat. Utilizarea blocului sincronizat reduce timpul de așteptare al firelor și îmbunătățește și performanța. În exemplul precedent, am folosit deja blocul sincronizat în timp ce am sincronizat codul nostru pentru prima dată.

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

Coordonarea firelor:

Pentru thread-urile sincronizate, comunicarea între fire este o sarcină importantă. Metodele încorporate care ajută la realizarea comunicării între fire pentru codul sincronizat sunt și anume:

  • aștepta()
  • notificare ()
  • notifyAll ()

Notă: Aceste metode aparțin clasei obiectului și nu clasei thread. Pentru ca un thread să poată invoca aceste metode pe un obiect, ar trebui să țină blocarea pe acel obiect. De asemenea, aceste metode determină ca un thread să-și elibereze blocarea pe obiectul pe care este invocat.

wait (): un thread despre invocarea metodei wait (), elibereaza blocarea pe obiect si trece in starea de asteptare. Are două suprasarcini de metodă:

  • public final void wait () aruncă InterruptException
  • public final void wait (timeout lung) aruncă InterruptException
  • public final void wait (timeout lung, int nanos) aruncă InterruptException

notificare (): Un fir trimite un semnal către un alt fir din starea de așteptare, folosind metoda notificării (). Acesta trimite notificarea unui singur thread astfel încât acest thread să își poată relua execuția. Ce thread va primi notificarea dintre toate firele aflate în starea de așteptare depinde de mașina virtuală Java.

  • public final null notification ()

notificationAll (): Când un thread invocă metoda notificationAll (), fiecare fir din starea sa de așteptare este notificat. Aceste thread-uri vor fi executate una după alta pe baza ordinului decis de Mașina Virtuală Java.

  • public final void notificationAll ()

Concluzie

În acest articol am văzut cum lucrul într-un mediu cu mai multe filete poate duce la inconsecvența datelor din cauza stării de cursă. Modul în care sincronizarea ne ajută să depășim acest lucru prin limitarea unui singur fir pentru a opera pe o resursă partajată simultan. De asemenea, modul în care firele sincronizate comunică între ele.

Articole recomandate:

Acesta a fost un ghid pentru Ce este sincronizarea în Java ?. Aici discutăm introducerea, înțelegerea, necesitatea, funcționarea și tipurile de sincronizare cu un cod de eșantion. Puteți parcurge și alte articole sugerate pentru a afla mai multe -

  1. Serializare în Java
  2. Ce este Generics în Java?
  3. Ce este API în Java?
  4. Ce este un arbore binar în Java?
  5. Exemple și mod de funcționare a genericilor în C #