Brug af et Mutex-objekt i Java

1. Oversigt

I denne vejledning ser vi forskellige måder at implementere en mutex på Java.

2. Mutex

I et multitrådet program skal to eller flere tråde muligvis få adgang til en delt ressource på samme tid, hvilket resulterer i uventet opførsel. Eksempler på sådanne delte ressourcer er datastrukturer, input-output-enheder, filer og netværksforbindelser.

Vi kalder dette scenario a race tilstand. Og den del af programmet, der får adgang til den delte ressource, er kendt som kritisk sektion. Så for at undgå en race-tilstand er vi nødt til at synkronisere adgangen til det kritiske afsnit.

En mutex (eller gensidig udelukkelse) er den enkleste type synkronisering - det sikrer, at kun en tråd kan udføre den kritiske del af et computerprogram ad gangen.

For at få adgang til en kritisk sektion erhverver en tråd mutex, derefter adgang til den kritiske sektion og frigiver endelig mutex. I mellemtiden, alle andre tråde blokerer, indtil mutex frigøres. Så snart en tråd forlader det kritiske afsnit, kan en anden tråd komme ind i det kritiske afsnit.

3. Hvorfor Mutex?

Lad os først tage et eksempel på en Sekvens Generator klasse, der genererer den næste sekvens ved at forøge nuværende værdi ved en hver gang:

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

Lad os nu oprette en test sag for at se, hvordan denne metode opfører sig, når flere tråde forsøger at få adgang til den samtidigt:

@Test offentlig ugyldighed givenUnsafeSequenceGenerator_whenRaceCondition_thenUnexpectedBehavior () kaster Undtagelse {int count = 1000; Indstil uniqueSequences = getUniqueSequences (ny SequenceGenerator (), count); Assert.assertEquals (count, uniqueSequences.size ()); } private Set getUniqueSequences (SequenceGenerator generator, int count) kaster Exception {ExecutorService executor = Executors.newFixedThreadPool (3); Indstil uniqueSequences = new LinkedHashSet (); Liste futures = ny 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 (); returner unikSekvenser; }

Når vi har udført denne testtilfælde, kan vi se, at den fejler det meste af tiden med den samme grund:

java.lang.AssertionError: forventet: men var: ved org.junit.Assert.fail (Assert.java:88) ved org.junit.Assert.failNotEquals (Assert.java:834) ved org.junit.Assert.assertEquals ( Assert.java:645)

Det unikSekvenser formodes at have størrelsen lig med det antal gange, vi har udført getNextSequence metode i vores test sag. Dette er dog ikke tilfældet på grund af løbetilstanden. Vi ønsker selvfølgelig ikke denne adfærd.

Så for at undgå sådanne race betingelser er vi nødt til det Sørg for, at kun en tråd kan udføre getNextSequence metode ad gangen. I sådanne scenarier kan vi bruge en mutex til at synkronisere trådene.

Der er forskellige måder, vi kan implementere en mutex i Java. Så næste vil vi se de forskellige måder at implementere en mutex til vores Sekvensgenerator klasse.

4. Brug synkroniseret Nøgleord

Først vil vi diskutere synkroniseret nøgleord, som er den enkleste måde at implementere en mutex på Java.

Hvert objekt i Java har en iboende lås tilknyttet. Detsynkroniseret metode ogdet synkroniseret blok brug denne iboende lås at begrænse adgangen til det kritiske afsnit til kun en tråd ad gangen.

Derfor, når en tråd påberåber sig en synkroniseret metode eller indtaster en synkroniseret blok, får den automatisk låsen. Låsen frigøres, når metoden eller blokken er færdig, eller en undtagelse kastes fra dem.

Lad os ændre getNextSequence at have en mutex, blot ved at tilføje synkroniseret nøgleord:

offentlig klasse SequenceGeneratorUsingSynchronizedMethod udvider SequenceGenerator {@Override public synchronized int getNextSequence () {return super.getNextSequence (); }}

Det synkroniseret blok svarer til synkroniseret metode, med mere kontrol over det kritiske afsnit og det objekt, vi kan bruge til at låse.

Så lad os nu se, hvordan vi kan brug synkroniseret blokere for at synkronisere på et brugerdefineret mutex-objekt:

offentlig klasse SequenceGeneratorUsingSynchronizedBlock udvider SequenceGenerator {privat Objekt mutex = nyt objekt (); @ Override offentlig int getNextSequence () {synkroniseret (mutex) {returner super.getNextSequence (); }}}

5. Brug ReentrantLock

Det ReentrantLock klasse blev introduceret i Java 1.5. Det giver mere fleksibilitet og kontrol end synkroniseret nøgleordstilgang.

Lad os se, hvordan vi kan bruge ReentrantLock for at opnå gensidig udelukkelse:

offentlig klasse SequenceGeneratorUsingReentrantLock udvider SequenceGenerator {private ReentrantLock mutex = ny ReentrantLock (); @ Override public int getNextSequence () {prøv {mutex.lock (); returner super.getNextSequence (); } endelig {mutex.unlock (); }}}

6. Brug Semafor

Synes godt om ReentrantLock, det Semafor klasse blev også introduceret i Java 1.5.

Mens der i tilfælde af en mutex kun en tråd har adgang til et kritisk afsnit, Semafor tillader et fast antal tråde for at få adgang til et kritisk afsnit. Derfor, Vi kan også implementere en mutex ved at indstille antallet af tilladte tråde i en Semafor til en.

Lad os nu oprette en anden trådsikker version af Sekvensgenerator ved brug af Semafor:

offentlig klasse SequenceGeneratorUsingSemaphore udvider SequenceGenerator {privat Semaphore mutex = ny Semaphore (1); @ Override public int getNextSequence () {prøv {mutex.acquire (); returner super.getNextSequence (); } fange (InterruptedException e) {// undtagelseshåndteringskode} endelig {mutex.release (); }}}

7. Brug af Guava Overvåge Klasse

Indtil videre har vi set mulighederne for at implementere mutex ved hjælp af funktioner leveret af Java.

Men den Overvåge klasse af Googles Guava-bibliotek er et bedre alternativ til ReentrantLock klasse. I henhold til dens dokumentation bruger du koden Overvåge er mere læselig og mindre udsat for fejl end den kode, der bruger ReentrantLock.

Først tilføjer vi Maven-afhængigheden for Guava:

 com.google.guava guava 28.0-jre 

Nu skriver vi en anden underklasse af Sekvensgenerator bruger Overvåge klasse:

offentlig klasse SequenceGeneratorUsingMonitor udvider SequenceGenerator {privat Monitor mutex = ny skærm (); @Override public int getNextSequence () {mutex.enter (); prøv {return super.getNextSequence (); } endelig {mutex.leave (); }}}

8. Konklusion

I denne vejledning har vi undersøgt konceptet med en mutex. Vi har også set de forskellige måder at implementere det på Java.

Som altid er den komplette kildekode til kodeeksemplerne, der bruges i denne vejledning, tilgængelig på GitHub.


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