Johdanto Java-synkronointiin

Synkronointi on Java-ominaisuus, joka estää useita ketjuja yrittämästä käyttää yleisesti jaettuja resursseja samanaikaisesti. Tässä jaetut resurssit viittaavat ulkoisiin tiedostoihin, luokan muuttujiin tai tietokantatietueisiin.

Synkronointia käytetään laajasti monisäikeisessä ohjelmoinnissa. ”Synkronoitu” on avainsana, joka antaa koodillesi mahdollisuuden, että vain yksi säie toimii siinä, häiritsemättä mitään toista säiettä kyseisenä ajanjaksona.

Miksi tarvitsemme synkronointia Java-sovelluksessa?

  • Java on monisäikeinen ohjelmointikieli. Tämä tarkoittaa, että kaksi tai useampi säie voi kulkea samanaikaisesti tehtävän suorittamiseen. Kun säikeet toimivat samanaikaisesti, on suuri todennäköisyys, että tapahtuu skenaario, jossa koodisi voi tarjota odottamattomia tuloksia.
  • Saatat ihmetellä, että jos monisäikeinen voi aiheuttaa virheellisiä tuloksia, miksi sitä pidetään tärkeänä ominaisuutena Javassa?
  • Monisäikeinen tekee koodisi nopeammaksi ajamalla useita säikeitä samanaikaisesti ja lyhentämällä näin koodien suoritusaikaa ja tarjoamalla suurta suorituskykyä. Monisäikeisen ympäristön käyttäminen johtaa kuitenkin epätarkkoihin tuloksiin, jotka johtuvat olosuhteista, joita yleisesti kutsutaan kilpailuolosuhteiksi.

Mikä on kilpailuolosuhteet?

Kun kaksi tai useampi ketju kulkee rinnakkain, niillä on taipumus käyttää ja muokata jaettuja resursseja tuona ajankohtana. Jaksoista, joissa säikeet suoritetaan, päätetään säikeiden ajoitusalgoritmissa.

Tästä syystä ei voida ennustaa, missä järjestyksessä ketjut suoritetaan, koska sitä säätelee yksinomaan säikeiden ajoittaja. Tämä vaikuttaa koodin ulostuloon ja johtaa epäjohdonmukaisiin lähtöihin. Koska useat kierteet kilpailevat keskenään toiminnan suorittamiseksi, ehtoa kutsutaan “kilpa-ehdoksi”.

Tarkastellaan esimerkiksi seuraavaa koodia:

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

Kun yllä olevaa koodia käytetään peräkkäin, ulostulot ovat seuraavat:

Ourput1:

Nykyinen säie toteutetaan säie 1 Nykyinen säie arvo 3

Nykyinen säie toteutetaan säie 3 Nykyinen säie arvo 2

Nykyinen säie toteutetaan säie 2 Nykyinen säie arvo 3

output2:

Nykyinen säie toteutetaan säie 3 Nykyinen säie arvo 3

Nykyinen säie toteutetaan säie 2 Nykyinen säie arvo 3

Nykyinen säie toteutetaan säie 1 Nykyinen säie arvo 3

output3:

Nykyinen säie toteutetaan säie 2 Nykyinen säie arvo 3

Nykyinen säie toteutetaan säie 1 Nykyinen säie arvo 3

Nykyinen säie toteutetaan säie 3 Nykyinen säie arvo 3

lähtö4:

Nykyinen säie suoritettavana säikeenä 1 Nykyisen säikeen arvo 2

Nykyinen säie toteutetaan säie 3 Nykyinen säie arvo 3

Nykyinen säie toteutetaan säie 2 Nykyinen säie arvo 2

  • Yllä olevasta esimerkistä voidaan päätellä, että säikeet suoritetaan satunnaisesti ja myös arvo on väärä. Meidän logiikkamme mukaan arvoa tulisi lisätä yhdellä. Kuitenkin tässä tapauksessa lähtöarvo on useimmissa tapauksissa 3 ja joissain tapauksissa 2.
  • Tässä “myVar” -muuttuja on jaettu resurssi, jolla useita säikeitä suoritetaan. Viestiketjut käyttävät ja muuttavat ”myVar” -arvoa samanaikaisesti. Katsotaanpa mitä tapahtuu, jos kommentoimme kahta muuta ketjua.

Tuotos tässä tapauksessa on:

Nykyinen ketju suoritetaan ketju 1 Nykyinen ketju arvo 1

Tämä tarkoittaa, että kun yksi säie on käynnissä, lähtö on odotetusti. Kuitenkin, kun useita säikeitä on käynnissä, arvoa muutetaan jokaisella säieellä. Siksi jaetun resurssin parissa olevien ketjujen lukumäärä on rajoitettava yhdeksi ketjuksi kerrallaan. Tämä saavutetaan synkronoinnin avulla.

Ymmärtäminen, mikä on Java-synkronointi

  • Synkronointi Java-sovelluksissa tapahtuu avainsanan ”synkronoitu” avulla. Tätä avainsanaa voidaan käyttää menetelmiin, lohkoihin tai objekteihin, mutta sitä ei voida käyttää luokkien ja muuttujien kanssa. Synkronoitu koodinpätkä sallii vain yhden säikeen käyttää ja muokata sitä tietyllä hetkellä.
  • Synkronoitu koodikappale vaikuttaa kuitenkin koodin suorituskykyyn, koska se pidentää muiden sitä käyttävien ketjujen odotusaikaa. Joten koodinpätkä tulisi synkronoida vain silloin, kun on mahdollista, että kilpailuolosuhteet ilmenevät. Jos ei, sen tulisi välttää.

Kuinka Java-synkronointi toimii sisäisesti?

  • Java-sisäinen synkronointi on toteutettu lukituskonseptin (tunnetaan myös nimellä näyttö) avulla. Jokaisella Java-objektilla on oma lukko. Synkronoidussa koodilohossa langan on hankittava lukko ennen kuin se voi suorittaa kyseisen koodilohkon. Kun lanka saa lukon, se voi suorittaa kyseisen koodin.
  • Suorituksen suorittamisen jälkeen se vapauttaa lukituksen automaattisesti. Jos toinen säie vaatii toimimaan synkronoidussa koodissa, se odottaa sitä käyttävää nykyistä säiettä vapauttaen lukon. Java-virtuaalikone huolehtii sisäisesti lukkojen hankkimisesta ja vapauttamisesta. Ohjelma ei ole vastuussa lukkojen hankkimisesta ja vapauttamisesta langan avulla. Jäljellä olevat ketjut voivat kuitenkin suorittaa minkä tahansa muun synkronoimattoman koodin pala samanaikaisesti.

Synkronoidaan aikaisempi esimerkkemme synkronoimalla koodi suoritustavan sisällä käyttämällä synkronoitua lohkoa luokassa ”Muokkaa” alla:

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

Luokan “RaceCondition” koodi pysyy samana. Nyt koodia ajatellen lähtö on seuraava:

output1:

Nykyinen ketju suoritetaan ketju 1 Nykyinen ketju arvo 1

Nykyinen ketju suoritetaan ketju 2 Nykyisen langan arvo 2

Nykyinen ketju suoritettavana säikeenä 3 Nykyisen langan arvo 3

output2:

Nykyinen ketju suoritetaan ketju 1 Nykyinen ketju arvo 1

Nykyinen lanka, jota suoritetaan, ketju 3 Nykyisen langan arvo 2

Nykyinen lanka, joka suoritetaan, ketju 2 Nykyisen langan arvo 3

Huomaa, että koodimme tarjoaa odotetun tuloksen. Tässä jokainen säie lisää arvoa 1 muuttujan “myVar” (luokassa “Muokkaa”) arvoa 1.

Huomaa: Synkronointi vaaditaan, kun useita ketjuja toimii samassa objektissa. Jos useita ketjuja toimii useissa kohteissa, synkronointia ei tarvita.

Esimerkiksi, muokataan koodi luokassa “RaceCondition” kuten alla ja työskentelemme aiemmin synkronoimattoman luokan “Modify” kanssa.

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

lähtö:

Nykyinen ketju suoritetaan ketju 1 Nykyinen ketju arvo 1

Nykyinen säie suoritettavana säikeenä 2 Nykyisen langan arvo 1

Nykyinen lanka, jota suoritetaan, ketju 3 Nykyisen langan arvo 1

Java-synkronoinnin tyypit:

Kierteiden synkronointia on kahta tyyppiä, joista toinen sulkee toisensa pois ja toinen ketjujen välinen viestintä.

1.Muutensa ulkopuolelle

  • Synkronoitu menetelmä.
  • Staattinen synkronoitu menetelmä
  • Synkronoitu lohko.

2.Kierrekoordinointi (ketjujen välinen viestintä javalla)

Keskinäisesti poissulkeva:

  • Tässä tapauksessa langat saavat lukon ennen käyttöä kohteella, välttäen siten työskentely kohteiden kanssa, joiden arvoja on käsitelty muiden ketjujen avulla.
  • Tämä voidaan saavuttaa kolmella tavalla:

i. Synkronoitu menetelmä: Voimme käyttää menetelmässä "synkronoitua" avainsanaa, jolloin siitä tulee synkronoitu menetelmä. Jokainen synkronoitua menetelmää käyttävä säie saa kyseisen objektin lukituksen ja vapauttaa sen, kun se on valmis. Yllä olevassa esimerkissä voimme tehdä “run ()” -menetelmämme synkronoituna käyttämällä ”synkronoitua” avainsanaa pääsymuuntajan jälkeen.

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

Tämän tapauksen tulos on:

Nykyinen ketju suoritetaan ketju 1 Nykyinen ketju arvo 1

Nykyinen lanka, jota suoritetaan, ketju 3 Nykyisen langan arvo 2

Nykyinen lanka, joka suoritetaan, ketju 2 Nykyisen langan arvo 3

ii. Staattinen synkronoitu menetelmä: Staattisten menetelmien synkronoimiseksi täytyy hankkia luokkatason lukko. Kun säie on saanut luokkatason lukituksen vasta, se pystyy suorittamaan staattisen menetelmän. Vaikka säiellä on luokkatason lukko, mikään muu säie ei voi suorittaa mitään muuta kyseisen luokan staattista synkronoitua menetelmää. Muut ketjut voivat kuitenkin suorittaa minkä tahansa muun luokan säännöllisen menetelmän tai säännöllisen staattisen menetelmän tai jopa ei-staattisen synkronoidun menetelmän.

Tarkastellaan esimerkiksi “Modify” -luokkaa ja tee siihen muutoksia muuntamalla “lisäys” -menetelmämme staattiseksi synkronoiduksi menetelmäksi. Koodimuutokset ovat seuraavat:

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. Synkronoitu lohko: Yksi synkronoidun menetelmän haitoista on, että se lisää säikeiden odotusaikaa, mikä vaikuttaa koodin suorituskykyyn. Siksi voidakseen synkronoida vain vaadittavat koodirivit koko menetelmän sijasta, on käytettävä synkronoitua lohkoa. Synkronoidun lohkon käyttö vähentää lankojen odotusaikaa ja parantaa myös suorituskykyä. Edellisessä esimerkissä olemme jo käyttäneet synkronoitua lohkoa synkronoidessamme koodia ensimmäistä kertaa.

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

Ketjujen koordinointi:

Synkronoiduissa lankoissa ketjujen välinen viestintä on tärkeä tehtävä. Sisäänrakennettuja menetelmiä, jotka auttavat saavuttamaan ketjujen välisen viestinnän synkronoidulle koodille, ovat:

  • odota()
  • ilmoittaa()
  • notifyAll ()

Huomaa: Nämä menetelmät kuuluvat objektiluokkaan eikä säieluokkaan. Jotta lanka voi vedota näihin menetelmiin objektissa, sen tulisi pitää kyseisen objektin lukitus. Lisäksi nämä menetelmät saavat langan vapauttamaan lukituksensa objektissa, johon sitä kutsutaan.

wait (): Lanka kutsuttaessa wait () -menetelmään, vapauttaa objektin lukituksen ja siirtyy odotustilaan. Sillä on kaksi menetelmän ylikuormitusta:

  • julkinen lopullinen tyhjä odotus () heittää InterruptedException
  • julkinen lopullinen mitätön odotus (pitkä aikakatkaisu) heittää InterruptedException
  • julkinen lopullinen mitätön odotus (pitkä aikakatkaisu, int nanos) heittää InterruptedException

ilmoita (): Viestiketju lähettää signaalin toiseen ketjuun odotustilassa käyttämällä ilmoitusta () -menetelmää. Se lähettää ilmoituksen vain yhdelle säikeelle siten, että tämä säie voi jatkaa suoritustaan. Mikä ketju vastaanottaa ilmoituksen kaikkien odotustilassa olevien ketjujen joukosta, riippuu Java-virtuaalikoneesta.

  • julkinen lopullinen mitätön ilmoitus ()

teavitaaAll (): Kun säie vetoaa NOTAll () -menetelmään, jokaisesta säikeestä odotustilassaan ilmoitetaan. Nämä ketjut suoritetaan peräkkäin Java-virtuaalikoneen päättämän järjestyksen perusteella.

  • julkinen lopullinen mitätön ilmoittaminenKaikki ()

johtopäätös

Tässä artikkelissa olemme nähneet, kuinka työskentely monisäikeisessä ympäristössä voi johtaa tietojen epäjohdonmukaisuuteen kilpailuolosuhteiden vuoksi. Kuinka synkronointi auttaa meitä voittamaan tämän rajoittamalla yhden säikeen toimimisen jaetulla resurssilla kerrallaan. Myös kuinka synkronoidut säikeet kommunikoivat keskenään.

Suositellut artikkelit:

Tämä on opas kohtaan Mikä on Java-synkronointi ?. Tässä keskustellaan johdannosta, ymmärryksestä, tarpeesta, työskentelystä ja synkronoinnin tyypeistä jonkin näytekoodin kanssa. Voit myös käydä läpi muiden ehdotettujen artikkeleidemme saadaksesi lisätietoja -

  1. Sarjakuva Java
  2. Mikä on Generic Java?
  3. Mikä on Java-sovellusliittymä?
  4. Mikä on binaaripuu Javassa?
  5. Esimerkkejä ja kuinka Generics toimii C #