Mutex objektum használata Java-ban

1. Áttekintés

Ebben az oktatóanyagban meglátjuk a mutex Java-ban történő megvalósításának különböző módjai.

2. Mutex

Többszálú alkalmazásban előfordulhat, hogy két vagy több szálnak egyszerre kell hozzáférnie egy megosztott erőforráshoz, ami váratlan viselkedést eredményez. Ilyen megosztott erőforrásokra példák az adatstruktúrák, az input-output eszközök, a fájlok és a hálózati kapcsolatok.

Ezt a forgatókönyvet a versenyfeltétel. És a programnak az a része, amely hozzáfér a megosztott erőforrásokhoz, a kritikus szakasz. Tehát a versenyfeltételek elkerülése érdekében szinkronizálnunk kell a kritikus szakaszhoz való hozzáférést.

A mutex (vagy kölcsönös kizárás) a legegyszerűbb típus szinkronizáló - azt biztosítja, hogy egyszerre csak egy szál tudja végrehajtani a számítógépes program kritikus szakaszát.

A kritikus szakasz eléréséhez egy szál megszerzi a mutexet, majd hozzáfér a kritikus szakaszhoz, és végül felszabadítja a mutexet. Eközben, az összes többi szál blokkolódik a mutex megjelenéséig. Amint egy szál kilép a kritikus szakaszból, egy másik szál léphet be a kritikus szakaszba.

3. Miért éppen a Mutex?

Először vegyünk egy példát a SequenceGeneraror osztály, amely a következő szekvenciát generálja a jelenlegi érték minden egyesével egyenként:

public class SequenceGenerator {private int currentValue = 0; public int getNextSequence () {currentValue = currentValue + 1; return currentValue; }}

Most hozzunk létre egy tesztesetet, hogy lássuk, hogyan viselkedik ez a módszer, amikor több szál egyidejűleg megpróbálja elérni:

@Test public void givenUnsafeSequenceGenerator_whenRaceCondition_thenUnexpectedBehavior () dob kivételt {int count = 1000; Set uniqueSequences = getUniqueSequences (új SequenceGenerator (), count); Assert.assertEquals (count, uniqueSequences.size ()); } private set getUniqueSequences (SequenceGenerator generator, int count) dobja a Kivételt {ExecutorService végrehajtó = Executors.newFixedThreadPool (3); Set uniqueSequences = új LinkedHashSet (); Lista határidős = new ArrayList (); for (int i = 0; i <count; i ++) {futures.add (végrehajtó.beküldés (generátor :: getNextSequence)); } a (Future future: futures) {uniqueSequences.add (future.get ()); } végrehajtó.awaitTermination (1, TimeUnit.SECONDS); végrehajtó.leállítás (); return uniqueSequences; }

Miután végrehajtottuk ezt a tesztesetet, láthatjuk, hogy legtöbbször kudarcot vall a következő okkal:

java.lang.AssertionError: várható: de volt: at org.junit.Assert.fail (Assert.java:88) at org.junit.Assert.failNotEquals (Assert.java:834) at org.junit.Assert.assertEquals ( Assert.java:645)

A uniqueSequences állítólag akkora, mint a végrehajtásainak száma getNextSequence módszer a tesztesetünkben. A versenyfeltételek miatt azonban ez nem így van. Nyilvánvaló, hogy nem akarjuk ezt a viselkedést.

Tehát az ilyen versenykörülmények elkerülése érdekében meg kell győződjön meg arról, hogy csak egy szál tudja végrehajtani a getNextSequence módszer egyszerre. Ilyen esetekben használhatunk egy mutexet a szálak szinkronizálásához.

Különböző módok léteznek, megvalósíthatunk egy mutex-et Java-ban. Tehát ezután megnézzük a mutex megvalósításának különböző módjait SequenceGenerator osztály.

4. Használata szinkronizált Kulcsszó

Először megbeszéljük a szinkronizált kulcsszó, amely a mutex Java-ban történő megvalósításának legegyszerűbb módja.

A Java minden objektumához tartozik egy belső zár. Aszinkronizált módszer ésa szinkronizált blokk használja ezt a belső zárat hogy a kritikus szakaszhoz való hozzáférést egyszerre csak egy szálra korlátozzuk.

Ezért, amikor egy szál a szinkronizált módszer vagy belép a szinkronizált blokkolja, automatikusan megszerzi a zárat. A zár akkor oldódik fel, amikor a metódus vagy a blokk befejeződik, vagy kivételt dobnak belőlük.

Változtassunk getNextSequence hogy legyen egy mutex, egyszerűen a szinkronizált kulcsszó:

public class SequenceGeneratorUsingSynchronizedMethod kiterjeszti a SequenceGenerator {@Override nyilvános szinkronizált int getNextSequence () {return super.getNextSequence (); }}

A szinkronizált blokk hasonló a szinkronizált módszerrel, jobban ellenőrizve a kritikus részt és a zároláshoz használható objektumot.

Tehát nézzük meg, hogyan tudunk használja a szinkronizált blokk egy egyéni mutex objektumon történő szinkronizáláshoz:

public class SequenceGeneratorUsingSynchronizedBlock kiterjeszti a SequenceGenerator {private Object mutex = new Object (); @Orride public int getNextSequence () {synchronized (mutex) {return super.getNextSequence (); }}}

5. Használata ReentrantLock

A ReentrantLock osztályt a Java 1.5-ben vezették be. Nagyobb rugalmasságot és ellenőrzést nyújt, mint a szinkronizált kulcsszó megközelítés.

Lássuk, hogyan használhatjuk a ReentrantLock a kölcsönös kirekesztés elérése érdekében:

public class SequenceGeneratorUsingReentrantLock kiterjeszti a SequenceGenerator {private ReentrantLock mutex = new ReentrantLock (); @Orride public int getNextSequence () {try {mutex.lock (); return super.getNextSequence (); } végül {mutex.unlock (); }}}

6. Használata Szemafor

Mint ReentrantLock, a Szemafor osztályt a Java 1.5-ben is bevezették.

Mutex esetén csak egy szál férhet hozzá a kritikus szakaszhoz, Szemafor lehetővé teszi rögzített számú szál a kritikus szakasz eléréséhez. Ebből kifolyólag, megvalósíthatunk egy mutexet is azáltal, hogy beállítjuk az a engedélyezett szálak számát Szemafor egyhez.

Most hozzunk létre egy másik szálbiztos verziót SequenceGenerator felhasználásával Szemafor:

public class SequenceGeneratorUsingSemaphore kiterjeszti a SequenceGenerator {private Semaphore mutex = új szemafor (1); @Orride public int getNextSequence () {try {mutex.acquire (); return super.getNextSequence (); } catch (InterruptedException e) {// kivételkezelő kód} végül {mutex.release (); }}}

7. Guava használata Monitor Osztály

Eddig láttuk a mutex megvalósításának lehetőségeit a Java által nyújtott szolgáltatások felhasználásával.

Azonban a Monitor osztálya a Google Guava könyvtárának jobb alternatívája a ReentrantLock osztály. A dokumentációja szerint kódoljon Monitor jobban olvasható és kevésbé hibára hajlamos, mint a kód használata ReentrantLock.

Először hozzáadjuk Guava Maven-függőségét:

 com.google.guava guava 28.0-jre 

Most egy újabb alosztályt írunk SequenceGenerator használni a Monitor osztály:

public class SequenceGeneratorUsingMonitor kiterjeszti a SequenceGenerator {private Monitor mutex = new Monitor (); @Orride public int getNextSequence () {mutex.enter (); próbáld ki a {return super.getNextSequence () parancsot; } végül {mutex.leave (); }}}

8. Következtetés

Ebben az oktatóanyagban megvizsgáltuk a mutex fogalmát. Láttuk a Java alkalmazásának különböző módjait is.

Mint mindig, az ebben az oktatóanyagban használt kód példák teljes forráskódja elérhető a GitHubon.