várjon és értesítsen () Methods in Java

1. Bemutatkozás

Ebben a cikkben megvizsgáljuk a Java egyik legalapvetőbb mechanizmusát - a szálak szinkronizálását.

Először megvitatunk néhány lényeges egyidejűséggel kapcsolatos kifejezést és módszertant.

És kifejlesztünk egy egyszerű alkalmazást - ahol a párhuzamossági kérdésekkel foglalkozunk a jobb megértés érdekében várjon() és értesít ().

2. Szálszinkronizálás Java-ban

Többszálas környezetben több szál megpróbálhatja módosítani ugyanazt az erőforrást. Ha a szálak kezelése nem megfelelő, ez természetesen konzisztencia-problémákhoz vezet.

2.1. Őrzött blokkok Java-ban

Az egyik eszköz, amellyel több szál műveleteit koordinálhatjuk a Java-ban, egy őrzött blokk. Az ilyen blokkok ellenőrzik az adott állapotot, mielőtt folytatnák a végrehajtást.

Ezt szem előtt tartva felhasználjuk a következőket:

  • Object.wait () - felfüggeszteni egy szálat
  • Object.notify () - hogy felébresszen egy szálat

Ezt jobban meg lehet érteni a következő ábrán, amely az a életciklusát ábrázolja cérna:

Felhívjuk figyelmét, hogy ennek az életciklusnak számos módja van; ebben a cikkben azonban csak a következőkre fogunk koncentrálni várjon() és értesít ().

3. Az várjon() Módszer

Egyszerűen fogalmazva, amikor hívunk várjon() - ez arra kényszeríti az aktuális szálat, hogy megvárja, amíg más szál meghív értesít () vagy értesítMinden () ugyanazon a tárgyon.

Ehhez az aktuális szálnak kell birtokolnia az objektum monitorját. Javadocs szerint ez akkor fordulhat elő, ha:

  • kivégeztük szinkronizált példány metódus az adott objektumhoz
  • kivégeztük a szinkronizált blokkolja az adott objektumot
  • kivégzéssel szinkronizált statikus típusú objektumok módszerei Osztály

Vegye figyelembe, hogy egyszerre csak egy aktív szál birtokolhatja az objektum monitorát.

Ez várjon() A módszer három túlterhelt aláírással rendelkezik. Vessünk egy pillantást ezekre.

3.1. várjon()

A várjon() A metódus az aktuális szálat korlátlan ideig várja, amíg egy másik szál meghív értesít () erre az objektumra ill értesítMinden ().

3.2. várakozás (hosszú időtúllépés)

Ezzel a módszerrel megadhatunk egy időkorlátot, amely után a szál automatikusan felébred. Egy szálat fel lehet ébreszteni, mielőtt elérné az időkorlátot értesít () vagy értesítMinden ().

Ne feledje, hogy hív várj (0) azonos a hívással várjon().

3.3. várakozás (hosszú időtúllépés, int nanók)

Ez egy újabb aláírás, amely ugyanazt a funkcionalitást biztosítja, az egyetlen különbség az, hogy nagyobb pontosságot tudunk biztosítani.

A teljes időtúllépés periódusát (nanoszekundumban) a következők szerint kell kiszámítani 1_000_000 * időtúllépés + nanó.

4. értesít () és értesítMinden ()

A értesít () metódust használnak azoknak a szálaknak az ébresztésére, amelyek hozzáférést várnak az objektum monitorjához.

Kétféleképpen lehet értesíteni a várakozó szálakat.

4.1. értesít ()

Az objektum monitorán várakozó összes szálhoz (a várjon() módszer), a módszer értesít () értesíti bármelyiket, hogy önkényesen ébredjen fel. Az ébresztés szálának kiválasztása nem determinista, és a megvalósítástól függ.

Mivel értesít () felébreszt egyetlen véletlenszerű szálat, amelyet fel lehet használni egymást kizáró zárolás megvalósítására, ahol a szálak hasonló feladatokat végeznek, de a legtöbb esetben életképesebb lenne végrehajtani értesítMinden ().

4.2. értesítMinden ()

Ez a módszer egyszerűen felébreszti az objektum monitorán várakozó összes szálat.

Az ébresztett szálak a szokásos módon fognak teljesülni - mint minden más szál.

De mielőtt hagynánk folytatni a kivégzésüket, mindig adjon meg egy gyors ellenőrzést a szál folytatásához szükséges állapotról - mert lehetnek olyan helyzetek, amikor a szál értesítés nélkül ébredt fel (ezt a forgatókönyvet később egy példában tárgyaljuk).

5. Feladó-vevő szinkronizálási probléma

Most, hogy megértettük az alapokat, menjünk át egy egyszerűn FeladóVevő alkalmazás - amely felhasználja a várjon() és értesít () módszerek a szinkronizálás beállítására közöttük:

  • A Feladó állítólag adatcsomagot küld a Vevő
  • A Vevő nem tudja feldolgozni az adatcsomagot, amíg a Feladó befejezte a küldését
  • Hasonlóképpen a Feladó csak akkor próbálkozhat újabb csomag küldésével, ha a Vevő már feldolgozta az előző csomagot

Alkossunk először Adat osztály, amely az adatokból áll csomag hogy elküldik Feladó nak nek Vevő. Majd használjuk várjon() és értesítMinden () szinkronizálás beállításához:

public class Data {private String csomag; // Igaz, ha a vevőnek várnia kell // Hamis, ha a küldőnek várnia kell a privát logikai átvitelt = igaz; public synchronized void send (String packet) {while (! transfer) {try {wait (); } catch (InterruptedException e) {Szál.currentThread (). megszakítás (); Log.error ("A szál megszakadt", e); }} transzfer = hamis; ez.csomag = csomag; értesítMinden (); } nyilvános szinkronizált karakterlánc fogadása () {while (átvitele) {try {wait (); } catch (InterruptedException e) {Szál.currentThread (). megszakítás (); Log.error ("A szál megszakadt", e); }} transzfer = igaz; értesítMinden (); visszaküldött csomag; }}

Bontjuk le, mi folyik itt:

  • A csomag változó a hálózaton keresztül továbbított adatokat jelöli
  • Nekünk van logikai változó transzfer - amely a Feladó és Vevő a szinkronizáláshoz használja:
    • Ha ez a változó igaz, aztán a Vevő várni kell rá Feladó hogy elküldje az üzenetet
    • Ha ez hamis, azután Feladó várni kell rá Vevő hogy megkapja az üzenetet
  • A Feladó használ Küld() módszer az adatok küldésére a Vevő:
    • Ha átruházás van hamis, várunk hívással várjon() ezen a szálon
    • De amikor van igaz, váltjuk az állapotot, beállítjuk az üzenetünket és felhívjuk értesítMinden () más szálak felébresztése annak meghatározása érdekében, hogy jelentős esemény történt, és ellenőrizhetik, hogy folytathatják-e a végrehajtást
  • Hasonlóképpen a Vevő használni fogja kap() módszer:
    • Ha a átruházás volt beállítva hamis által Feladó, akkor csak ez folytatódik, különben felhívunk várjon() ezen a szálon
    • Amikor a feltétel teljesül, váltjuk az állapotot, értesítjük az összes várakozó szálat, hogy felébredjünk, és visszaküldjük az adatcsomagot, amely Vevő

5.1. Miért zárja be várjon() a míg Hurok?

Mivel értesít () és értesítMinden () véletlenszerűen felébreszti az objektum monitorán várakozó szálakat, nem mindig fontos, hogy a feltétel teljesüljön. Néha előfordulhat, hogy a szál felébred, de a feltétel még nem teljesül.

Meghatározhatunk egy ellenőrzést is, hogy megmentsen minket a hamis ébresztéstől - ahol egy szál úgy ébredhet fel a várakozástól, hogy soha nem kapott értesítést.

5.2. Miért kell szinkronizálnunk az s-t?vége () és kap() Mód?

Ezeket a módszereket belül helyeztük el szinkronizált módszerek a belső zárak biztosítására. Ha egy szál hív várjon() A módszer nem rendelkezik a benne rejlő zárral, hibát dob.

Most létrehozunk Feladó és Vevő és végrehajtja a Futható interfész mindkét oldalon, hogy példányaikat egy szál végrehajthassa.

Először nézzük meg, hogyan Feladó működni fog:

public class A Sender Runnable {private Data data; // standard konstruktorok public void run () {String packets [] = {"Első csomag", "Második csomag", "Harmadik csomag", "Negyedik csomag", "Vége"}; for (Karakterlánc csomag: csomagok) {data.send (csomag); // Thread.sleep () a szerveroldali nehéz feldolgozás utánzásához próbálja ki a {Thread.sleep (ThreadLocalRandom.current (). NextInt (1000, 5000)); } catch (InterruptedException e) {Szál.currentThread (). megszakítás (); Log.error ("A szál megszakadt", e); }}}}

Ezért Feladó:

  • Létrehozunk néhány véletlenszerű adatcsomagot, amelyeket a hálózat egész területén elküld csomagok [] sor
  • Minden csomag esetében csak telefonálunk Küld()
  • Akkor hívunk Thread.sleep () véletlenszerű időközzel a szerveroldali nehéz feldolgozás utánzásához

Végül valósítsuk meg a mi Vevő:

public class Receiver megvalósítja Runnable {private Data load; // szabványos konstruktorok public void run () {for (Karaktersorozat kapottMessage = load.receive ();! "End" .egyenlő (kapottMessage); kapottMessage = load.receive ()) {System.out.println (vastuvõzettMessage); // ... próbáld ki a {Thread.sleep (ThreadLocalRandom.current (). nextInt (1000, 5000)); } catch (InterruptedException e) {Szál.currentThread (). megszakítás (); Log.error ("A szál megszakadt", e); }}}}

Itt egyszerűen hívunk load.receive () a hurokban, amíg meg nem kapjuk az utolsót „Vég” adatcsomag.

Lássuk most ezt az alkalmazást működés közben:

public static void main (String [] args) {Data data = new Data (); Szál küldő = new Szál (új Feladó (adatok)); Menetvevő = új Menet (új Vevő (adat)); sender.start (); vevő.indítás (); }

A következő kimenetet kapjuk:

Első csomag Második csomag Harmadik csomag Negyedik csomag 

És itt vagyunk - az összes adatcsomagot megfelelő sorrendben kaptuk meg és sikeresen létrehozta a helyes kommunikációt a feladónk és a fogadónk között.

6. Következtetés

Ebben a cikkben megvitattunk néhány alapvető szinkronizálási koncepciót a Java-ban; pontosabban arra összpontosítottunk, hogy miként tudjuk használni várjon() és értesít () érdekes szinkronizálási problémák megoldására. És végül átmentünk egy kódmintán, ahol ezeket a fogalmakat a gyakorlatban alkalmaztuk.

Mielőtt itt véget vetnénk, érdemes megemlíteni, hogy mindezek az alacsony szintű API-k, mint pl várjon(), értesít () és értesítMinden () - olyan hagyományos módszerek, amelyek jól működnek, de a magasabb szintű mechanizmusok gyakran egyszerűbbek és jobbak - például a Java őshonos Zár és Feltétel interfészek (elérhető java.util.concurrent.locks csomag).

További információ a java.util.egyidejű csomagot, tekintse át a java.util.concurrent cikk áttekintését, és Zár és Feltétel a java.util.concurrent.Lockok című útmutató ismerteti.

Mint mindig, a cikkben használt teljes kódrészletek elérhetőek a GitHubon.