Používanie objektu Mutex v Jave

1. Prehľad

V tomto návode uvidíme rôznymi spôsobmi implementácie mutexu v Jave.

2. Mutex

Vo viacvláknovej aplikácii môžu dve alebo viaceré vlákna potrebovať prístup k zdieľanému prostriedku súčasne, čo vedie k neočakávanému správaniu. Príklady takýchto zdieľaných zdrojov sú dátové štruktúry, vstupno-výstupné zariadenia, súbory a sieťové pripojenia.

Tento scenár nazývame a stav rasy. A časť programu, ktorá pristupuje k zdieľanému prostriedku, je známa ako kritický úsek. Aby sme sa vyhli podmienkam rasy, musíme synchronizovať prístup do kritickej sekcie.

Mutex (alebo vzájomné vylúčenie) je najjednoduchší typ synchronizátor - to zaisťuje, že kritickú časť počítačového programu môže súčasne vykonávať iba jedno vlákno.

Pre prístup do kritickej sekcie vlákno získa mutex, potom vstúpi do kritickej sekcie a nakoniec uvoľní mutex. Medzitým, všetky ostatné vlákna blokujú, kým sa mutex neuvoľní. Hneď ako vlákno opustí kritickú časť, môže do tejto časti vstúpiť ďalšie vlákno.

3. Prečo Mutex?

Najprv si vezmime príklad a Generátor sekvencií triedy, ktorá generuje nasledujúcu sekvenciu zvýšením hodnoty súčasná hodnota zakaždým o jednu:

verejná trieda SequenceGenerator {private int currentValue = 0; public int getNextSequence () {currentValue = currentValue + 1; návratová aktuálna hodnota; }}

Teraz vytvorme testovací prípad, aby sme zistili, ako sa táto metóda chová, keď sa k nej pokúša získať prístup viac vlákien súčasne:

@ Test public void givenUnsafeSequenceGenerator_whenRaceCondition_thenUnexpectedBehavior () vyvolá výnimku {int count = 1000; Nastaviť uniqueSequences = getUniqueSequences (nový SequenceGenerator (), počet); Assert.assertEquals (count, uniqueSequences.size ()); } private Set getUniqueSequences (generátor SequenceGenerator, počet int) vyvolá Exception {ExecutorService executor = Executors.newFixedThreadPool (3); Nastaviť uniqueSequences = new LinkedHashSet (); Zoznam futures = nový ArrayList (); for (int i = 0; i <count; i ++) {futures.add (executor.submit (generator :: getNextSequence)); } for (Future future: futures) {uniqueSequences.add (future.get ()); } executor.awaitTermination (1, TimeUnit.SECONDS); executor.shutdown (); vrátiť uniqueSequences; }

Po vykonaní tohto testovacieho prípadu vidíme, že väčšinu času zlyháva z podobného dôvodu:

java.lang.AssertionError: expect: but was: at org.junit.Assert.fail (Assert.java:88) at org.junit.Assert.failNotEquals (Assert.java:834) at org.junit.Assert.assertEquals ( Assert.java:645)

The uniqueSequences má mať veľkosť rovnú počtu pokusov, ktoré sme vykonali getNextSequence metóda v našom testovacom prípade. Inak to však nie je kvôli stavu rasy. Je zrejmé, že toto správanie nechceme.

Aby sme sa vyhli takýmto závodným podmienkam, musíme uistite sa, že iba jedno vlákno môže vykonávať getNextSequence metóda naraz. V takýchto scenároch môžeme na synchronizáciu vlákien použiť mutex.

Existuje mnoho spôsobov, ako môžeme implementovať mutex v Jave. Ďalej teda uvidíme rôzne spôsoby implementácie mutexu pre naše Generátor sekvencií trieda.

4. Používanie synchronizované Kľúčové slovo

Najskôr si rozoberieme synchronizované kľúčové slovo, čo je najjednoduchší spôsob implementácie mutexu v Jave.

Ku každému objektu v Jave je priradený vnútorný zámok. Thesynchronizované metóda athe synchronizované blok použije tento vnútorný zámok obmedziť prístup kritickej sekcie iba na jedno vlákno súčasne.

Preto keď vlákno vyvolá a synchronizované metóda alebo zadá a synchronizované blok, automaticky získa zámok. Zámok sa uvoľní, keď sa metóda alebo blok dokončí alebo sa z nich vyhodí výnimka.

Poďme sa zmeniť getNextSequence mať mutex, jednoducho pridaním synchronizované kľúčové slovo:

verejná trieda SequenceGeneratorUsingSynchronizedMethod rozširuje SequenceGenerator {@Override public synchronized int getNextSequence () {return super.getNextSequence (); }}

The synchronizované blok je podobný synchronizované metóda, s väčšou kontrolou nad kritickým úsekom a objektom, ktorý môžeme použiť na uzamknutie.

Poďme sa teda pozrieť, ako to môžeme Použi synchronizované blok na synchronizáciu na vlastnom objekte mutex:

public class SequenceGeneratorUsingSynchronizedBlock rozširuje SequenceGenerator {private Object mutex = new Object (); @Override public int getNextSequence () {synchronized (mutex) {return super.getNextSequence (); }}}

5. Používanie ReentrantLock

The ReentrantLock triedy bola predstavená v prostredí Java 1.5. Poskytuje väčšiu flexibilitu a kontrolu ako synchronizované prístup podľa kľúčových slov.

Pozrime sa, ako môžeme použiť ReentrantLock na dosiahnutie vzájomného vylúčenia:

public class SequenceGeneratorUsingReentrantLock rozširuje SequenceGenerator {private ReentrantLock mutex = nový ReentrantLock (); @Override public int getNextSequence () {try {mutex.lock (); návrat super.getNextSequence (); } nakoniec {mutex.unlock (); }}}

6. Používanie Semafor

Páči sa mi to ReentrantLock, Semafor trieda bola predstavená aj v Jave 1.5.

Zatiaľ čo v prípade mutexu má prístup do kritickej sekcie iba jedno vlákno, Semafor umožňuje pevný počet vlákien pre prístup do kritickej sekcie. Preto môžeme tiež implementovať mutex nastavením počtu povolených vlákien v a Semafor do jedného.

Poďme teraz vytvoriť ďalšiu verziu vlákna bezpečnú pre vlákna Generátor sekvencií použitím Semafor:

verejná trieda SequenceGeneratorUsingSemaphore rozširuje SequenceGenerator {súkromný semafor mutex = nový semafor (1); @Override public int getNextSequence () {try {mutex.acquire (); návrat super.getNextSequence (); } catch (InterruptedException e) {// kód na spracovanie výnimiek} konečne {mutex.release (); }}}

7. Používanie Guava Monitor Trieda

Doteraz sme videli možnosti implementácie mutexu pomocou funkcií poskytovaných Java.

Avšak Monitor trieda knižnice Guava spoločnosti Google je lepšou alternatívou k ReentrantLock trieda. Podľa jeho dokumentácie použite kód Monitor je čitateľnejšia a menej náchylná na chyby ako kód, ktorý používa ReentrantLock.

Najskôr pridáme závislosť Maven pre Guavu:

 com.google.guava guava 28.0-jre 

Teraz napíšeme ďalšiu podtriedu Generátor sekvencií pomocou Monitor trieda:

public class SequenceGeneratorUsingMonitor rozširuje SequenceGenerator {private Monitor mutex = new Monitor (); @Override public int getNextSequence () {mutex.enter (); try {return super.getNextSequence (); } konečne {mutex.leave (); }}}

8. Záver

V tomto tutoriáli sme sa pozreli na koncept mutexu. Tiež sme videli rôzne spôsoby implementácie v Jave.

Kompletný zdrojový kód príkladov kódov použitých v tomto tutoriále je ako vždy k dispozícii na GitHub.


$config[zx-auto] not found$config[zx-overlay] not found