CodeGym /Java blog /Véletlen /Jobb együtt: Java és a Thread osztály. II. rész – Szinkro...
John Squirrels
Szint
San Francisco

Jobb együtt: Java és a Thread osztály. II. rész – Szinkronizálás

Megjelent a csoportban

Bevezetés

Tehát tudjuk, hogy a Java-nak vannak szálai. Erről a Jobb együtt: Java és a szál osztály című ismertetőben olvashat . I. rész – A végrehajtás szálai . A szálak szükségesek a párhuzamos munkavégzéshez. Ez nagyon valószínűvé teszi, hogy a szálak valamilyen módon kölcsönhatásba lépnek egymással. Nézzük meg, hogyan történik ez, és milyen alapvető eszközeink vannak. Jobb együtt: Java és a Thread osztály.  II. rész – Szinkronizálás – 1

Hozam

A Thread.yield() zavarba ejtő és ritkán használatos. Az interneten sokféleképpen leírják. Beleértve néhány embert, aki azt írja, hogy van néhány szálsor, amelyben egy szál a szálak prioritásai alapján ereszkedik le. Mások azt írják, hogy egy szál állapotát "Futó"-ról "Futható"-ra változtatja (annak ellenére, hogy nincs különbség ezek között az állapotok között, azaz a Java nem tesz különbséget közöttük). A valóság az, hogy mindez sokkal kevésbé ismert, de bizonyos értelemben egyszerűbb. Jobb együtt: Java és a Thread osztály.  II. rész – Szinkronizálás – 2Egy hiba ( JDK-6416721: (spec thread) Fix Thread.yield() javadoc ) van naplózva a yield()metódus dokumentációjában. Ha elolvasod, egyértelmű, hogy ayield()metódus valójában csak néhány ajánlást ad a Java szálütemezőnek, hogy ennek a szálnak kevesebb végrehajtási időt kell adni. De hogy valójában mi történik, vagyis hogy az ütemező az ajánlás szerint cselekszik-e, és általában mit tesz, az a JVM megvalósításától és az operációs rendszertől függ. És ez más tényezőktől is függhet. Valószínűleg minden zűrzavar annak a ténynek köszönhető, hogy a Java nyelv fejlődésével újragondolták a többszálat. Olvasson többet az áttekintésben itt: A Java rövid bemutatása Thread.yield() .

Alvás

Egy szál aludhat a végrehajtása közben. Ez a legegyszerűbb interakció más szálakkal. A Java kódunkat futtató Java virtuális gépet futtató operációs rendszer saját szálütemezővel rendelkezik . Ez dönti el, hogy melyik szálat és mikor indítsa el. A programozó nem léphet kapcsolatba ezzel az ütemezővel közvetlenül a Java kódból, csak a JVM-en keresztül. Megkérheti az ütemezőt, hogy szüneteltesse a szálat egy időre, azaz altassa el. Bővebben ezekben a cikkekben olvashat: Thread.sleep() és How Multithreading működik . Azt is megnézheti, hogyan működnek a szálak a Windows operációs rendszerekben: A Windows Thread belső részei . És most lássuk a saját szemünkkel. Mentse el a következő kódot egy nevű fájlba HelloWorldApp.java:

class HelloWorldApp {
    public static void main(String []args) {
        Runnable task = () -> {
            try {
                int secToWait = 1000 * 60;
                Thread.currentThread().sleep(secToWait);
                System.out.println("Woke up");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        Thread thread = new Thread(task);
        thread.start();
    }
}
Amint látja, van néhány feladatunk, amely 60 másodpercig vár, utána a program véget ér. A " " paranccsal lefordítjuk javac HelloWorldApp.java, majd a " java HelloWorldApp" paranccsal futtatjuk a programot. A legjobb, ha a programot külön ablakban indítjuk el. Például Windows alatt ez így van: start java HelloWorldApp. A jps paranccsal kapjuk meg a PID-t (process ID), és a szálak listáját a " jvisualvm --openpid pid: Jobb együtt: Java és a Thread osztály.  II. rész – Szinkronizálás – 3Amint látja, a mi szálunk most "Alvó" állapotú. Valójában van egy elegánsabb módja is a segítségnek. a mi szálunknak szép álmai vannak:

try {
	TimeUnit.SECONDS.sleep(60);
	System.out.println("Woke up");
} catch (InterruptedException e) {
	e.printStackTrace();
}
Észrevetted, hogy InterruptedExceptionmindenhol intézkedünk? Értsük meg, miért.

Thread.interrupt()

Az a helyzet, hogy amíg egy szál vár/alszik, valaki meg akarja szakítani. Ebben az esetben egy InterruptedException. Ez a mechanizmus azután jött létre, hogy a Thread.stop()metódust elavultnak, azaz elavultnak és nemkívánatosnak nyilvánították. Ennek oka az volt stop(), hogy a metódus meghívásakor a szál egyszerűen "megölték", ami nagyon kiszámíthatatlan volt. Nem tudhattuk, hogy a szál mikor áll le, és nem tudtuk garantálni az adatok konzisztenciáját. Képzelje el, hogy adatokat ír egy fájlba, miközben a szál megszakad. A Java készítői ahelyett, hogy megölték volna a szálat, úgy döntöttek, hogy logikusabb lenne azt mondani neki, hogy meg kell szakítani. Az, hogy miként reagálunk ezekre az információkra, maga a szál dönti el. További részletekért olvassa el a Miért elavult a Thread.stop című részt?az Oracle honlapján. Nézzünk egy példát:

public static void main(String []args) {
	Runnable task = () -> {
		try {
			TimeUnit.SECONDS.sleep(60);
		} catch (InterruptedException e) {
			System.out.println("Interrupted");
		}
	};
	Thread thread = new Thread(task);
	thread.start();
	thread.interrupt();
}
Ebben a példában nem várunk 60 másodpercet. Ehelyett azonnal megjelenik a „Megszakítva” felirat. Ez azért van, mert meghívtuk a interrupt()metódust a szálon. Ez a módszer beállít egy belső jelzőt, az úgynevezett "megszakítási állapotot". Vagyis minden szálnak van egy belső jelzője, amely közvetlenül nem érhető el. De vannak natív módszereink a zászlóval való interakcióhoz. De nem ez az egyetlen módja. Lehet, hogy egy szál fut, nem vár valamire, egyszerűen csak műveleteket hajt végre. De arra számíthat, hogy mások egy adott időpontban be akarják fejezni a munkáját. Például:

public static void main(String []args) {
	Runnable task = () -> {
		while(!Thread.currentThread().isInterrupted()) {
			// Do some work
		}
		System.out.println("Finished");
	};
	Thread thread = new Thread(task);
	thread.start();
	thread.interrupt();
}
A fenti példában a whileciklus addig hajtódik végre, amíg a szál kívülről meg nem szakad. Ami a isInterruptedzászlót illeti, fontos tudni, hogy ha elkapunk egy -t InterruptedException, akkor az isInterrupted jelző visszaáll, majd isInterrupted()false értéket ad vissza. A Thread osztálynak van egy statikus Thread.interrupted() metódusa is, amely csak az aktuális szálra vonatkozik, de ez a metódus visszaállítja a jelzőt false értékre! Bővebben ebben a Szálmegszakítás című fejezetben olvashat .

Csatlakozás (Várja meg, amíg egy másik szál befejeződik)

A legegyszerűbb típusú várakozás egy másik szál befejezésére vár.

public static void main(String []args) throws InterruptedException {
	Runnable task = () -> {
		try {
			TimeUnit.SECONDS.sleep(5);
		} catch (InterruptedException e) {
			System.out.println("Interrupted");
		}
	};
	Thread thread = new Thread(task);
	thread.start();
	thread.join();
	System.out.println("Finished");
}
Ebben a példában az új szál 5 másodpercig aludni fog. Ugyanakkor a főszál megvárja, amíg az alvó szál felébred és befejezi munkáját. Ha megnézi a szál állapotát a JVisualVM-ben, akkor az így fog kinézni: Jobb együtt: Java és a Thread osztály.  II. rész – Szinkronizálás – 4A megfigyelő eszközöknek köszönhetően láthatja, hogy mi történik a szálal. A joinmódszer meglehetősen egyszerű, mert ez csak egy Java kóddal rendelkező metódus, amely wait()addig fut, amíg a szál, amelyen hívják, él. Amint a szál elhal (amikor befejezte a munkáját), a várakozás megszakad. És ez a join()módszer varázsa. Tehát térjünk át a legérdekesebb dologra.

Monitor

A multithreading magában foglalja a monitor fogalmát. A monitor szó a 16. századi latinból származik az angolból, és jelentése "egy folyamat megfigyelésére, ellenőrzésére vagy folyamatos nyilvántartására használt műszer vagy eszköz". E cikk keretében megpróbáljuk lefedni az alapokat. Aki a részletekre vágyik, bátran vágjon bele a linkelt anyagokba. Útunkat a Java nyelvi specifikációval (JLS) kezdjük: 17.1. Szinkronizálás . A következőt írja ki: Jobb együtt: Java és a Thread osztály.  II. rész – Szinkronizálás – 5Kiderült, hogy a Java "monitor" mechanizmust használ a szálak közötti szinkronizáláshoz. Minden objektumhoz egy monitor van hozzárendelve, és a szálak a -val megszerezhetik, lock()illetve a segítségével felszabadíthatják unlock(). Ezután megtaláljuk az oktatóanyagot az Oracle webhelyén: Intrinsic Locks and Synchronization. Ez az oktatóanyag azt mondja, hogy a Java szinkronizálása egy belső entitás köré épül, amelyet belső zárnak vagy monitorzárnak neveznek . Ezt a zárat gyakran egyszerűen " monitornak " nevezik . Azt is látjuk, hogy a Java minden objektumához belső zár tartozik. Olvassa el a Java - Intrinsic Locks and Synchronization című részt . Ezután fontos megérteni, hogyan lehet egy Java objektumot a monitorhoz társítani. A Java-ban minden objektumnak van egy fejléce, amely a programozó számára nem elérhető belső metaadatokat tárolja a kódból, de amelyre a virtuális gépnek szüksége van az objektumokkal való helyes működéshez. Az objektum fejléce tartalmaz egy "jelölőszót", amely így néz ki: Jobb együtt: Java és a Thread osztály.  II. rész – Szinkronizálás – 6

https://edu.netbeans.org/contrib/slides/java-overview-and-java-se6.pdf

Íme egy nagyon hasznos JavaWorld-cikk: Hogyan hajtja végre a Java virtuális gép a szálszinkronizálást ? Ezt a cikket kombinálni kell a JDK hibakövető rendszer következő problémájának „Összegzés” szakaszában található leírással: JDK-8183909 . Ugyanezt olvashatja itt: JEP-8183909 . Tehát a Java-ban a monitor egy objektumhoz van társítva, és egy szál blokkolására szolgál, amikor a szál megpróbálja megszerezni (vagy megszerezni) a zárolást. Íme a legegyszerűbb példa:

public class HelloWorld{
    public static void main(String []args){
        Object object = new Object();
        synchronized(object) {
            System.out.println("Hello World");
        }
    }
}
Itt az aktuális szál (az, amelyiken ezek a kódsorok végrehajtásra kerülnek) a synchronizedkulcsszó használatával megpróbálja használni aobject"\változó a zár megszerzéséhez/megszerzéséhez. Ha senki más nem verseng a monitorért (azaz senki más nem futtat szinkronizált kódot ugyanazzal az objektummal), akkor a Java megpróbálhatja végrehajtani a "biased locking" nevű optimalizálást. Az objektum fejlécében a jelölőszóhoz hozzá kell adni egy releváns címkét és egy rekordot arról, hogy melyik szál birtokolja a monitor zárját. Ez csökkenti a monitor lezárásához szükséges többletköltséget. Ha a monitor korábban egy másik szál tulajdonában volt, akkor az ilyen zárolás nem elegendő. A JVM a következő típusú zárolásra vált: „alapzárás”. Összehasonlítás és csere (CAS) műveleteket használ. Ráadásul maga az objektumfejléc jelölőszava már nem tárolja a jelölőszót, hanem hivatkozást a tárolási helyére, és a címke megváltozik, így a JVM megérti, hogy alapvető zárolást használunk. Ha több szál verseng (mérkőzik) egy monitorért (az egyik megszerezte a zárolást, a másik pedig a zár feloldására vár), akkor a jelölőszóban lévő címke megváltozik, és a jelölőszó mostantól hivatkozást tárol a monitorra mint objektum – a JVM valamilyen belső entitása. A JDK bővítési javaslatában (JEP) leírtak szerint ez a helyzet a memória Native Heap területén helyet igényel az entitás tárolásához. A belső entitás memóriahelyére való hivatkozás az objektum fejlécének jelölőszójában kerül tárolásra. Így a monitor valójában egy mechanizmus a megosztott erőforrásokhoz való hozzáférés szinkronizálására több szál között. A JVM e mechanizmus több megvalósítása között vált. Tehát az egyszerűség kedvéért, amikor a monitorról beszélünk, valójában zárakról beszélünk. és egy másodperc a zár feloldására vár), majd a jelölőszóban lévő címke megváltozik, és a jelölőszó most objektumként tárolja a monitorra mutató hivatkozást – a JVM valamilyen belső entitását. A JDK bővítési javaslatában (JEP) leírtak szerint ez a helyzet a memória Native Heap területén helyet igényel az entitás tárolásához. A belső entitás memóriahelyére való hivatkozás az objektum fejlécének jelölőszójában kerül tárolásra. Így a monitor valójában egy mechanizmus a megosztott erőforrásokhoz való hozzáférés szinkronizálására több szál között. A JVM e mechanizmus több megvalósítása között vált. Tehát az egyszerűség kedvéért, amikor a monitorról beszélünk, valójában zárakról beszélünk. és egy másodperc a zár feloldására vár), majd a jelölőszóban lévő címke megváltozik, és a jelölőszó most objektumként tárolja a monitorra mutató hivatkozást – a JVM valamilyen belső entitását. A JDK bővítési javaslatában (JEP) leírtak szerint ez a helyzet a memória Native Heap területén helyet igényel az entitás tárolásához. A belső entitás memóriahelyére való hivatkozás az objektum fejlécének jelölőszójában kerül tárolásra. Így a monitor valójában egy mechanizmus a megosztott erőforrásokhoz való hozzáférés szinkronizálására több szál között. A JVM e mechanizmus több megvalósítása között vált. Tehát az egyszerűség kedvéért, amikor a monitorról beszélünk, valójában zárakról beszélünk. és a jelölőszó most objektumként tárolja a monitorra mutató hivatkozást – a JVM valamilyen belső entitását. A JDK bővítési javaslatában (JEP) leírtak szerint ez a helyzet a memória Native Heap területén helyet igényel az entitás tárolásához. A belső entitás memóriahelyére való hivatkozás az objektum fejlécének jelölőszójában kerül tárolásra. Így a monitor valójában egy mechanizmus a megosztott erőforrásokhoz való hozzáférés szinkronizálására több szál között. A JVM e mechanizmus több megvalósítása között vált. Tehát az egyszerűség kedvéért, amikor a monitorról beszélünk, valójában zárakról beszélünk. és a jelölőszó most objektumként tárolja a monitorra mutató hivatkozást – a JVM valamilyen belső entitását. A JDK bővítési javaslatában (JEP) leírtak szerint ez a helyzet a memória Native Heap területén helyet igényel az entitás tárolásához. A belső entitás memóriahelyére való hivatkozás az objektum fejlécének jelölőszójában kerül tárolásra. Így a monitor valójában egy mechanizmus a megosztott erőforrásokhoz való hozzáférés szinkronizálására több szál között. A JVM e mechanizmus több megvalósítása között vált. Tehát az egyszerűség kedvéért, amikor a monitorról beszélünk, valójában zárakról beszélünk. A belső entitás memóriahelyére való hivatkozás az objektum fejlécének jelölőszójában kerül tárolásra. Így a monitor valójában egy mechanizmus a megosztott erőforrásokhoz való hozzáférés szinkronizálására több szál között. A JVM e mechanizmus több megvalósítása között vált. Tehát az egyszerűség kedvéért, amikor a monitorról beszélünk, valójában zárakról beszélünk. A belső entitás memóriahelyére való hivatkozás az objektum fejlécének jelölőszójában kerül tárolásra. Így a monitor valójában egy mechanizmus a megosztott erőforrásokhoz való hozzáférés szinkronizálására több szál között. A JVM e mechanizmus több megvalósítása között vált. Tehát az egyszerűség kedvéért, amikor a monitorról beszélünk, valójában zárakról beszélünk. Jobb együtt: Java és a Thread osztály.  II. rész – Szinkronizálás – 7

Szinkronizálva (vár a zárra)

Ahogy korábban láttuk, a "szinkronizált blokk" (vagy "kritikus szakasz") fogalma szorosan kapcsolódik a monitor fogalmához. Vessen egy pillantást egy példára:

public static void main(String[] args) throws InterruptedException {
	Object lock = new Object();

	Runnable task = () -> {
		synchronized(lock) {
			System.out.println("thread");
		}
	};

	Thread th1 = new Thread(task);
	th1.start();
	synchronized(lock) {
		for (int i = 0; i < 8; i++) {
			Thread.currentThread().sleep(1000);
			System.out.print(" " + i);
		}
		System.out.println(" ...");
	}
}
Itt a főszál először átadja a feladat objektumot az új szálnak, majd azonnal megszerzi a zárolást és egy hosszú műveletet hajt végre vele (8 másodperc). Ez idő alatt a feladat nem tud továbbhaladni, mert nem tud belépni a synchronizedblokkba, mert a zár már megvan. Ha a szál nem tudja elérni a zárat, akkor vár a monitorra. Amint megkapja a zárolást, folytatja a végrehajtást. Amikor egy szál kilép a monitorból, feloldja a zárat. A JVisualVM-ben ez így néz ki: Jobb együtt: Java és a Thread osztály.  II. rész – Szinkronizálás – 8Amint a JVisualVM-ben is látható, az állapot "Monitor", vagyis a szál blokkolva van, és nem tudja átvenni a monitort. A kóddal is meghatározhatja a szál állapotát, de az így meghatározott állapotnevek nem egyeznek a JVisualVM-ben használt nevekkel, bár hasonlóak. Ebben az esetben ath1.getState()Az utasítás a for ciklusban a BLOCKED értéket adja vissza , mert amíg a ciklus fut, az lockobjektum monitorát a szál foglalja el main, a th1szál pedig blokkolva van, és nem folytathatja a zárolás feloldásáig. A szinkronizált blokkok mellett egy egész metódus szinkronizálható. Például itt van egy metódus az osztályból HashTable:

public synchronized int size() {
	return count;
}
Ezt a módszert egy adott időpontban csak egy szál hajtja végre. Valóban szükségünk van a zárra? Igen, szükségünk van rá. Példánymetódusok esetén a "this" objektum (aktuális objektum) zárként működik. Érdekes vita folyik itt erről a témáról: Van-e előnye a szinkronizált módszer használatának szinkronizált blokk helyett? . Ha a metódus statikus, akkor a zár nem a "this" objektum lesz (mivel a statikus metódushoz nincs "this" objektum), hanem egy osztály objektum (például ) Integer.class.

Várjon (monitorra vár). notify() és notifyAll() metódusok

A Thread osztálynak van egy másik várakozási metódusa, amely egy monitorhoz van társítva. sleep()A és a -val ellentétben join()ez a módszer nem hívható egyszerűen. A neve wait(). A waitmetódust a monitorhoz társított objektum hívja meg, amelyre várni akarunk. Lássunk egy példát:

public static void main(String []args) throws InterruptedException {
	    Object lock = new Object();
	    // The task object will wait until it is notified via lock
	    Runnable task = () -> {
	        synchronized(lock) {
	            try {
	                lock.wait();
	            } catch(InterruptedException e) {
	                System.out.println("interrupted");
	            }
	        }
	        // After we are notified, we will wait until we can acquire the lock
	        System.out.println("thread");
	    };
	    Thread taskThread = new Thread(task);
	    taskThread.start();
        // We sleep. Then we acquire the lock, notify, and release the lock
	    Thread.currentThread().sleep(3000);
	    System.out.println("main");
	    synchronized(lock) {
	        lock.notify();
	    }
}
A JVisualVM-ben ez így néz ki: Jobb együtt: Java és a Thread osztály.  II. rész – Szinkronizálás – 10A működés megértéséhez ne feledje, hogy a wait()és notify()metódusok a következőhöz vannak társítva java.lang.Object. Furcsának tűnhet, hogy a szálakhoz kapcsolódó metódusok vannak az Objectosztályban. De ennek oka most kiderül. Emlékszel, hogy a Java-ban minden objektumnak van fejléce. A fejléc különféle háztartási információkat tartalmaz, beleértve a monitorral kapcsolatos információkat, azaz a zár állapotát. Ne feledje, hogy minden objektum vagy osztálypéldány egy belső entitáshoz van társítva a JVM-ben, amelyet belső zárnak vagy monitornak neveznek. A fenti példában a feladatobjektum kódja azt jelzi, hogy megadjuk az objektumhoz társított monitor szinkronizált blokkját lock. Ha sikerül megszereznünk a zárat ehhez a monitorhoz, akkorwait()nak, nek hívják. A feladatot végrehajtó szál felszabadítja az lockobjektum monitorát, de belép a szálak sorába, amelyek értesítésre várnak az lockobjektum monitorától. Ezt a szálsort WAIT SET-nek nevezik, ami jobban tükrözi a célját. Vagyis inkább egy készlet, mint egy sor. A mainszál létrehoz egy új szálat a feladatobjektummal, elindítja, és vár 3 másodpercet. Ez nagyon valószínűvé teszi, hogy az új szál meg tudja szerezni a zárolást a mainszál előtt, és bekerüljön a monitor sorába. Ezt követően mainmaga a szál belép az lockobjektum szinkronizált blokkjába, és a monitor segítségével szálértesítést hajt végre. Az értesítés elküldése után a mainszál felszabadítja alocklockaz objektum monitorját, és az új szál, amely korábban az objektum monitorjának felszabadítására várt , folytatja a végrehajtást. Lehetséges értesítést küldeni csak egy szálnak ( notify()), vagy egyidejűleg a sorban lévő összes szálnak ( notifyAll()). Bővebben itt: A notify() és a notifyAll() közötti különbség a Java-ban . Fontos megjegyezni, hogy az értesítési sorrend a JVM megvalósítási módjától függ. Bővebben itt: Hogyan lehet megoldani az éhezést a notify és notifyAll segítségével? . A szinkronizálás objektum megadása nélkül is végrehajtható. Ezt akkor teheti meg, ha egy teljes metódust szinkronizál, nem pedig egyetlen kódblokkot. Például statikus metódusok esetén a zár egy Class objektum lesz (a következőn keresztül szerezhető be .class):

public static synchronized void printA() {
	System.out.println("A");
}
public static void printB() {
	synchronized(HelloWorld.class) {
		System.out.println("B");
	}
}
A zárak használatában mindkét módszer azonos. instanceHa egy metódus nem statikus, akkor a szinkronizálás az aktuális , azaz a segítségével történik this. Mellesleg, korábban azt mondtuk, hogy a getState()módszerrel lekérheti egy szál állapotát. Például a sorban lévő, figyelőre váró szál esetében az állapot VÁRAKOZÁS vagy TIMED_WAITING lesz, ha a wait()metódus időtúllépést adott meg. Jobb együtt: Java és a Thread osztály.  II. rész – Szinkronizálás – 11

https://stackoverflow.com/questions/36425942/what-is-the-lifecycle-of-thread-in-java

A szál életciklusa

Egy szál állapota élete során megváltozik. Valójában ezek a változások magukban foglalják a szál életciklusát. Amint létrejön egy szál, állapota ÚJ. Ebben az állapotban az új szál még nem fut, és a Java szálütemező még nem tud róla semmit. Ahhoz, hogy a szálütemező megismerje a szálat, meg kell hívnia a thread.start()metódust. Ezután a szál FUTTATÁS állapotba kerül. Az interneten sok helytelen diagram található, amelyek megkülönböztetik a "Futtatható" és a "Futó" állapotokat. De ez tévedés, mert a Java nem tesz különbséget a "munkára kész" (futható) és a "dolgozó" (futó) között. Ha egy szál él, de nem aktív (nem futtatható), akkor a következő két állapot egyikében van:
  • BLOKKOLVA — várakozás a kritikus szakaszba, azaz egy synchronizedblokkba való belépésre.
  • VÁRAKOZÁS – vár egy másik szálra, amely megfelel valamilyen feltételnek.
Ha a feltétel teljesül, akkor a szálütemező elindítja a szálat. Ha a szál egy meghatározott ideig vár, akkor az állapota TIMED_WAITING. Ha a szál már nem fut (befejeződött vagy kivétel történt), akkor MEGSZŰNT állapotba kerül. A szál állapotának megállapításához használja a getState()módszert. A szálaknak van egy isAlive()metódusa is, amely igazat ad vissza, ha a szál nem TERMINÁLT.

LockSupport és menetparkoló

A Java 1.6-tól kezdve megjelent egy érdekes mechanizmus, a LockSupport . Jobb együtt: Java és a Thread osztály.  II. rész – Szinkronizálás – 12Ez az osztály egy "engedélyt" társít minden egyes szálhoz, amely ezt használja. A metódus hívása park()azonnal visszatér, ha az engedély rendelkezésre áll, és az engedélyt a folyamat során felhasználja. Ellenkező esetben blokkol. A módszer meghívásával unparkelérhetővé válik az engedély, ha az még nem elérhető. Csak 1 engedély van. A Java dokumentáció LockSupportaz Semaphoreosztályra hivatkozik. Nézzünk egy egyszerű példát:

import java.util.concurrent.Semaphore;
public class HelloWorldApp{
    
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(0);
        try {
            semaphore.acquire();
        } catch (InterruptedException e) {
            // Request the permit and wait until we get it
            e.printStackTrace();
        }
        System.out.println("Hello, World!");
    }
}
Ez a kód mindig várni fog, mert most a szemafornak 0 engedélye van. És amikor acquire()a kód be van hívva (azaz engedélyt kér), a szál megvárja, amíg megkapja az engedélyt. Mivel várunk, kezelnünk kell InterruptedException. Érdekes módon a szemafor külön szálállapotot kap. Ha megnézzük a JVisualVM-et, látni fogjuk, hogy az állapot nem "Várj", hanem "Park". Jobb együtt: Java és a Thread osztály.  II. rész – Szinkronizálás – 13Nézzünk egy másik példát:

public static void main(String[] args) throws InterruptedException {
        Runnable task = () -> {
            // Park the current thread
            System.err.println("Will be Parked");
            LockSupport.park();
            // As soon as we are unparked, we will start to act
            System.err.println("Unparked");
        };
        Thread th = new Thread(task);
        th.start();
        Thread.currentThread().sleep(2000);
        System.err.println("Thread state: " + th.getState());
        
        LockSupport.unpark(th);
        Thread.currentThread().sleep(2000);
}
A szál állapota VÁRAKOZÁS lesz, de a JVisualVM különbséget tesz waita synchronizedkulcsszó és parkaz LockSupportosztály között. Miért olyan fontos ez LockSupport? Ismét a Java dokumentációhoz fordulunk, és megnézzük a WAITING szál állapotát. Amint látja, csak háromféleképpen lehet bekerülni. Ezek közül kettő wait()a és join(). A harmadik pedig az LockSupport. A Java-ban a zárak t-re is építhetők, LockSupporés magasabb szintű eszközöket kínálnak. Próbáljunk meg egyet használni. Például nézze meg ReentrantLock:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class HelloWorld{

    public static void main(String []args) throws InterruptedException {
        Lock lock = new ReentrantLock();
        Runnable task = () -> {
            lock.lock();
            System.out.println("Thread");
            lock.unlock();
        };
        lock.lock();

        Thread th = new Thread(task);
        th.start();
        System.out.println("main");
        Thread.currentThread().sleep(2000);
        lock.unlock();
    }
}
Csakúgy, mint az előző példákban, itt is minden egyszerű. Az lockobjektum arra vár, hogy valaki felszabadítsa a megosztott erőforrást. Ha megnézzük a JVisualVM-et, látni fogjuk, hogy az új szál addig parkolva lesz, amíg a mainszál fel nem oldja a zárolást. A zárolásokról itt olvashat bővebben: Java 8 StampedLocks vs. ReadWriteLocks és Synchronized and Lock API Java-ban. A zárolások megvalósításának jobb megértése érdekében hasznos elolvasni a Phaserről ebben a cikkben: Útmutató a Java Phaserhez . És ha a különféle szinkronizálókról beszélünk, el kell olvasnia a DZone The Java Synchronizers című cikkét .

Következtetés

Ebben az áttekintésben megvizsgáltuk a szálak Java-ban való interakciójának fő módjait. Kiegészítő anyag: Jobb együtt: Java és a Thread osztály. I. rész – A végrehajtás szálai Jobb együtt: Java és a Thread osztály. III. rész – Interakció Jobb együtt: Java és a szál osztály. IV. rész – Hívható, jövő és barátok Jobb együtt: Java és a szál osztály. V. rész – Végrehajtó, ThreadPool, Fork/Join Better together: Java és a Thread osztály. VI. rész – Tüzet el!
Hozzászólások
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION