Párhuzamosság Java nyelven

A Java programozási nyelvet és a JVM -et ( Java Virtual Machine ) úgy tervezték, hogy támogassa a párhuzamos számítást , és minden számítás egy szál kontextusában történik . Több szál is megoszthat objektumokat és erőforrásokat; minden szál végrehajtja a saját utasításait (kódját), de potenciálisan hozzáférhet a program bármely objektumához. A programozó feladata a koordináció (vagy " szinkronizálás ")") szálak a megosztott objektumok olvasási és írási műveletei során. Szálszinkronizálásra azért van szükség, hogy egyszerre csak egy szál férhessen hozzá egy objektumhoz, és megakadályozza, hogy a szálak hozzáférjenek a nem teljesen frissített objektumokhoz, miközben egy másik szál dolgozik rajtuk. A Java nyelv beépített szálszinkronizálást támogató konstrukciókkal rendelkezik.

Folyamatok és szálak

A Java virtuális gép legtöbb megvalósítása egyetlen folyamatot használ a program futtatásához, és a Java programozási nyelvben a párhuzamos számítást leggyakrabban szálakhoz társítják . A szálakat néha könnyű folyamatoknak is nevezik .

Tárgyak streamelése

A szálak megosztják egymással a folyamaterőforrásokat, például a memóriát és a megnyitott fájlokat. Ez a megközelítés hatékony, de potenciálisan problémás kommunikációhoz vezet. Minden alkalmazásnak van legalább egy futó szála. Azt a szálat, amelyből a program végrehajtása kezdődik, főnek vagy főnek nevezzük . A főszál képes további szálakat létrehozni objektumok Runnablevagy Callable. (A felület Callablehasonló abban Runnable, hogy mindkettő olyan osztályokhoz készült, amelyek egy külön szálon lesznek példányosítva. Runnable, azonban nem ad vissza eredményt, és nem dobhat ellenőrzött kivételt .)

Mindegyik szál úgy ütemezhető, hogy külön CPU-magon fusson, egyetlen processzormagon időszeletelést használjon, vagy több processzoron időszeletelést alkalmazzon. Az utolsó két esetben a rendszer időszakosan vált a szálak között, felváltva lehetővé téve az egyik vagy a másik szál végrehajtását. Ezt a sémát pszeudo-párhuzamosságnak nevezik. Nincs olyan univerzális megoldás, amely pontosan megmondaná, hogy a Java szálak hogyan lesznek OS natív szálakká konvertálva. Ez az adott JVM megvalósítástól függ.

A Java nyelvben egy szál a Thread. Ez az osztály szabványos menetvágó mechanizmusokat foglal magában. A szálak közvetlenül vagy absztrakt mechanizmusokon keresztül kezelhetők, mint például az Executor és a java.util.concurrent csomagból származó gyűjtemények.

Téma futtatása

Kétféleképpen indíthat új szálat:

  • A Runnable felület megvalósítása
public class HelloRunnable implements Runnable { public void run () { System . ki . println ( "Üdvözlet a szálból!" ); } public static void main ( String [] args ) { ( new Thread ( new HelloRunnable ())). start (); } }
  • Öröklődés a szál osztályból
public class HelloThread extends Thread { public void run () { System . ki . println ( "Üdvözlet a szálból!" ); } public static void main ( String [] args ) { ( new HelloThread ()). start (); } } Megszakítja

A megszakítás azt jelzi egy szálnak, hogy le kell állítania az aktuális munkát, és valami mást kell tennie. Egy szál megszakítást küldhet az objektum interrupt()Thread metódusának meghívásával , ha meg kell szakítania a hozzá tartozó szálat. A megszakítási mechanizmus az osztály belső megszakítási állapotának (megszakítási jelzőjének) segítségével valósul meg Thread. A Thread.interrupt() meghívása felemeli ezt a jelzőt. Megállapodás szerint minden olyan metódus, amely InterruptedException -re végződik, visszaállítja a megszakítási jelzőt. Kétféleképpen ellenőrizheti, hogy ez a jelző be van-e állítva. Az első mód a szálobjektum bool isInterrupted() metódusának meghívása , a második mód a statikus bool Thread.interrupted() metódus meghívása . Az első módszer visszaadja a megszakításjelző állapotát, és érintetlenül hagyja ezt a jelzőt. A második módszer visszaadja a zászló állapotát, és visszaállítja azt. Ne feledje, hogy a Thread.interrupted()  az osztály statikus metódusa Thread, és a meghívása annak a szálnak a megszakítási jelzőjének értékét adja vissza, amelyből meghívták.

Befejezésre vár

A Java olyan mechanizmust biztosít, amely lehetővé teszi, hogy az egyik szál megvárja a másik szál végrehajtását. Ehhez a Thread.join() metódust használjuk .

Démonok

Java-ban egy folyamat akkor fejeződik be, amikor az utolsó szál véget ér. Még akkor is, ha a main() metódus már befejeződött, de az általa létrehozott szálak még futnak, a rendszer megvárja azok befejezését. Ez a szabály azonban nem vonatkozik egy speciális száltípusra - a démonokra. Ha a folyamat utolsó normál szála megszakadt, és csak démonszálak maradtak, akkor azok kényszerített leállításra kerülnek, és a folyamat véget ér. Leggyakrabban a démonszálakat olyan háttérfeladatok végrehajtására használják, amelyek egy folyamatot annak élettartama alatt kiszolgálnak.

Egy szál démonként való deklarálása meglehetősen egyszerű – a szál indítása előtt meg kell hívni a setDaemon(true) metódust ; A logikai isDaemon() metódus meghívásával ellenőrizheti, hogy egy szál démon-e .

Kivételek

Egy kidobott és kezeletlen kivétel a szál leállását okozza. A fő szál automatikusan kiírja a kivételt a konzolra, és a felhasználó által létrehozott szálak ezt csak kezelő regisztrálásával tehetik meg. [1] [2]

Memóriamodell

A Java memóriamodell [1] a szálak kölcsönhatását írja le a memórián keresztül a Java programozási nyelvben. A modern számítógépeken gyakran a kód végrehajtása nem abban a sorrendben történik, ahogyan azt írták, a gyorsaság érdekében. A permutációt a fordító , a processzor és a memória alrendszer végzi el . A Java programozási nyelv nem garantálja a műveletek atomitását és a szekvenciális konzisztenciát a megosztott objektumok mezőinek olvasása vagy írása során. Ez a megoldás felszabadítja a fordító kezét, és lehetővé teszi a memóriaelérési műveletek permutációján alapuló optimalizálást (például regiszterlefoglalást , gyakori részkifejezések eltávolítását és redundáns olvasási műveletek kiküszöbölését ). [3]

Szinkronizálás

A szálak úgy kommunikálnak, hogy megosztják a hozzáférést a mezőkhöz és a mezők által hivatkozott objektumokhoz. Ez a kommunikációs forma rendkívül hatékony, de kétféle hibát tesz lehetővé: szálinterferenciát és memóriakonzisztencia hibát. Előfordulásuk megelőzése érdekében van egy szinkronizációs mechanizmus.

Az átrendezés (átrendezés, átrendezés) a helytelenül szinkronizált többszálú programokban nyilvánul meg , ahol az egyik szál megfigyelheti a többi szál által keltett hatást, és az ilyen programok képesek lehetnek észlelni, hogy a változók frissített értékei egy másik szálban láthatóvá válnak. sorrendben a forráskódban meghatározottnál.

A Java szálak szinkronizálásához monitorokat használnak , amelyek egy magas szintű mechanizmus, amely egyszerre csak egy szálat tesz lehetővé, hogy egy monitor által védett kódblokkot hajtson végre. A monitorok viselkedését a zárak szempontjából veszik figyelembe ; Minden objektumhoz egy zár tartozik.

A szinkronizálásnak több aspektusa van. A legjobban érthető a kölcsönös kizárás – csak egy szál birtokolhat monitort, így a monitoron történő szinkronizálás azt jelenti, hogy amint egy szál belép a monitor által védett szinkronizált blokkba, egyetlen másik szál sem léphet be a monitor által védett blokkba az első szálig. kilép a szinkronizált blokkból.

A szinkronizálás azonban több, mint a kölcsönös kizárás. A szinkronizálás biztosítja, hogy a szinkronizált blokk előtt vagy azon belül a memóriába írt adatok láthatóak legyenek az ugyanazon a monitoron szinkronizált többi szál számára. Miután kiléptünk a szinkronizált blokkból, felszabadítjuk a monitort, aminek az a hatása, hogy a gyorsítótárat a főmemóriába öblítjük, így a szálunk által írt írások láthatóak lesznek a többi szál számára. Mielőtt belépnénk a szinkronizált blokkba, megszerezzük a monitort, ami érvényteleníti a helyi processzor gyorsítótárát, így a változók a fő memóriából töltődnek be. Ekkor láthatjuk a monitor előző kiadása által láthatóvá tett összes bejegyzést. (JSR 133)

A mezőn történő olvasás-írás atomi művelet , ha a mezőt illékonynak nyilvánították, vagy egy egyedi zárolás védi, amelyet bármilyen olvasás-írás előtt szereztek be.

Zárak és szinkronizált blokkok

A kölcsönös kizárás és a szálszinkronizálás hatását egy olyan szinkronizált blokk vagy metódus megadásával érik el, amely implicit módon szerzi meg a zárolást, vagy a zárolás explicit megszerzésével (például ReentrantLocka java.util.concurrent.locks csomagból). Mindkét megközelítés ugyanolyan hatással van a memória viselkedésére. Ha egy bizonyos mezőhöz minden hozzáférési kísérletet ugyanaz a zár véd, akkor ennek a mezőnek az írási-olvasási műveletei atomi .

Változó mezők

A mezőkre alkalmazva a kulcsszó volatilegarantálja:

  1. (A Java minden verziójában) A -változóhoz való hozzáférések volatileglobálisan vannak rendezve. Ez azt jelenti, hogy minden szál , amely hozzáfér a volatile-mezőhöz, a folytatás előtt kiolvassa az értékét, ahelyett, hogy (ha lehetséges) a gyorsítótárazott értéket használná. ( volatileA -változókhoz való hozzáférést nem lehet egymással átrendezni, de a közönséges változókhoz való hozzáféréssel átrendezhetők. Ez tagadja a -mezők hasznosságát volatileaz egyik szálról a másikra történő jelzés eszközeként.)
  2. (Java 5 és újabb verziókban) A -mezőbe való írás volatileugyanolyan hatással van a memóriára, mint a monitor kiadása , míg az olvasás ugyanolyan hatással van, mint a monitor megszerzése .  A -mező elérése létrehozza az " előtt történik " kapcsolatot . [4] Lényegében ez a reláció a garancia arra, hogy ami a szál számára látható volt, amikor a -mezőbe írt, az a szál számára láthatóvá válik, amikor olvassa a . volatile AvolatilefBf

Volatile-a mezők atomi. A volatile-mezőből történő olvasásnak ugyanaz a hatása, mint a zárolásnak: a munkamemóriában lévő adatokat érvénytelennek nyilvánítja, és a volatile-mező értékét újra beolvassa a memóriából. A -mezőbe való írás volatileugyanolyan hatással van a memóriára, mint a zárolás feloldása: volatile-a -mező azonnal a memóriába kerül.

Utolsó mezők

A véglegesnek nyilvánított mezőt véglegesnek nevezzük, és az inicializálás után nem módosítható. Az objektum végső mezői a konstruktorban inicializálódnak. Ha a konstruktor bizonyos egyszerű szabályokat követ, akkor a végső mező helyes értéke szinkronizálás nélkül látható lesz a többi szál számára. Egy egyszerű szabály az, hogy ez a hivatkozás nem hagyhatja el a konstruktort, amíg be nem fejeződött.

Történelem

A JDK 1.2 -től kezdve a Java szabványos Java Collections Framework gyűjteményosztályokat tartalmaz .

Doug Lee , aki a Java Collections Framework megvalósításában is közreműködött, kifejlesztette a párhuzamossági csomagot , amely számos szinkronizálási primitívet és nagyszámú gyűjteményhez kapcsolódó osztályt tartalmaz. [5] A munkálatokat a JSR 166 [6] részeként folytatták Doug Lee elnökletével .

A JDK 5.0 kiadás számos kiegészítést és pontosítást tartalmazott a Java párhuzamossági modellhez. Először kerültek be a JDK-ba a JSR 166 által fejlesztett párhuzamossági API-k. A JSR 133 jól meghatározott atomi műveletekhez nyújtott támogatást többszálú/többprocesszoros környezetben.

A Java SE 6 és a Java SE 7 egyaránt változásokat és kiegészítéseket hoz a JSR 166 API-hoz.

Lásd még

Jegyzetek

  1. Oracle Interface Thread.UncaughtExceptionHandler . Letöltve: 2014. május 10. Az eredetiből archiválva : 2014. május 12.
  2. Silent Thread halál a kezeletlen kivételektől . readjava.com . Letöltve: 2014. május 10. Az eredetiből archiválva : 2014. május 12.
  3. Herlihy, Maurice és Nir Shavit. "A többprocesszoros programozás művészete." PODC. Vol. 6. 2006.
  4. 17.4.4. szakasz: Szinkronizálási sorrend A Java® nyelvi specifikáció, Java SE 7 kiadás . Oracle Corporation (2013). Letöltve: 2013. május 12. Az eredetiből archiválva : 2021. február 3.
  5. Doug Lee . Az util.concurrent 1.3.4-es kiadás csomag áttekintése . — « Megjegyzés: A J2SE 5.0 kiadásakor ez a csomag karbantartási módba lép: Csak a lényeges javítások kerülnek kiadásra. A java.util.concurrent J2SE5 csomag a csomag fő összetevőinek továbbfejlesztett, hatékonyabb, szabványosított verzióit tartalmazza. ". Letöltve: 2011. január 1. Az eredetiből archiválva : 2020. december 18.
  6. JSR 166: Concurrency Utilities (a hivatkozás nem elérhető) . Letöltve: 2015. november 3. Az eredetiből archiválva : 2016. november 3.. 

Linkek

  • Goetz, Brian; Joshua Bloch; Bowbeer József; Doug Lea; David Holmes Tim Peierls. Java párhuzamosság a gyakorlatban  (neopr.) . - Addison Wesley , 2006. - ISBN 0-321-34960-1 .
  • Leah, Doug. Párhuzamos programozás Java nyelven : Tervezési alapelvek és minták  . - Addison Wesley , 1999. - ISBN 0-201-31009-0 .

Hivatkozások külső forrásokhoz