Bruke et Mutex-objekt i Java

1. Oversikt

I denne opplæringen får vi se forskjellige måter å implementere en mutex på Java.

2. Mutex

I et multitrådet program kan det hende at to eller flere tråder må ha tilgang til en delt ressurs samtidig, noe som resulterer i uventet oppførsel. Eksempler på slike delte ressurser er datastrukturer, inngangsutgangsenheter, filer og nettverkstilkoblinger.

Vi kaller dette scenariet a løpstilstand. Og den delen av programmet som får tilgang til den delte ressursen er kjent som kritisk seksjon. Så, for å unngå løpstilstand, må vi synkronisere tilgangen til den kritiske delen.

En mutex (eller gjensidig ekskludering) er den enkleste typen synkronisering - den sørger for at bare en tråd kan utføre den kritiske delen av et dataprogram om gangen.

For å få tilgang til en kritisk del, anskaffer en tråd mutex, deretter tilgang til den kritiske seksjonen, og til slutt frigjør mutex. I mellomtiden, alle andre tråder blokkeres til mutex frigjøres. Så snart en tråd går ut av den kritiske delen, kan en annen tråd komme inn i den kritiske delen.

3. Hvorfor Mutex?

La oss først ta et eksempel på en Sekvens Generator klasse, som genererer neste sekvens ved å øke nåværende verdi av en hver gang:

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

La oss nå lage en testtilfelle for å se hvordan denne metoden oppfører seg når flere tråder prøver å få tilgang til den samtidig:

@Test offentlig ugyldighet givenUnsafeSequenceGenerator_whenRaceCondition_thenUnexpectedBehavior () kaster Unntak {int count = 1000; Sett unikeSekvenser = getUniqueSequences (ny SequenceGenerator (), count); Assert.assertEquals (count, uniqueSequences.size ()); } private Set getUniqueSequences (SequenceGenerator generator, int count) kaster Unntak {ExecutorService executor = Executors.newFixedThreadPool (3); Sett unikeSekvenser = nye 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 unikeSekvenser; }

Når vi har utført denne testsaken, kan vi se at den mislykkes mesteparten av tiden med den samme årsaken:

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)

De unikeSekvenser skal ha størrelsen lik antall ganger vi har utført getNextSequence metoden i testsaken vår. Dette er imidlertid ikke tilfelle på grunn av løpetilstanden. Åpenbart ønsker vi ikke denne oppførselen.

Så for å unngå slike løpsforhold, må vi sørg for at bare en tråd kan utføre getNextSequence metode om gangen. I slike scenarier kan vi bruke en mutex til å synkronisere trådene.

Det er forskjellige måter, vi kan implementere en mutex i Java. Så vil vi se de forskjellige måtene å implementere en mutex for vår Sekvensgenerator klasse.

4. Bruke synkronisert Nøkkelord

Først skal vi diskutere synkronisert nøkkelord, som er den enkleste måten å implementere en mutex på Java.

Hvert objekt i Java har en egenlås tilknyttet. Desynkronisert metode ogde synkronisert blokker, bruk denne egenslåsen å begrense tilgangen til den kritiske delen til bare en tråd om gangen.

Derfor, når en tråd påkaller a synkronisert metode eller går inn i en synkronisert blokkere, får den automatisk låsen. Låsen utløses når metoden eller blokken er fullført eller et unntak kastes fra dem.

La oss endre getNextSequence å ha en mutex, ganske enkelt ved å legge til synkronisert nøkkelord:

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

De synkronisert blokken ligner på synkronisert metode, med mer kontroll over den kritiske delen og objektet vi kan bruke til å låse.

Så, la oss nå se hvordan vi kan bruke synkronisert blokker for å synkronisere på et tilpasset mutex-objekt:

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

5. Bruke ReentrantLock

De ReentrantLock klasse ble introdusert i Java 1.5. Det gir mer fleksibilitet og kontroll enn synkronisert søkeordtilnærming.

La oss se hvordan vi kan bruke ReentrantLock for å oppnå gjensidig utestenging:

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

6. Bruke Semafor

Som ReentrantLock, den Semafor klasse ble også introdusert i Java 1.5.

Mens det i tilfelle en mutex bare en tråd har tilgang til en kritisk seksjon, Semafor tillater et fast antall tråder for å få tilgang til en kritisk seksjon. Derfor, vi kan også implementere en mutex ved å angi antall tillatte tråder i a Semafor til en.

La oss nå lage en annen trådsikker versjon av Sekvensgenerator ved hjelp av Semafor:

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

7. Bruke guava Observere Klasse

Så langt har vi sett alternativene for å implementere mutex ved hjelp av funksjoner levert av Java.

Imidlertid, den Observere klasse av Googles Guava-bibliotek er et bedre alternativ til ReentrantLock klasse. I henhold til dokumentasjonen bruker du koden Observere er mer leselig og mindre utsatt for feil enn koden som brukes ReentrantLock.

Først legger vi til Maven-avhengigheten for Guava:

 com.google.guava guava 28.0-jre 

Nå skal vi skrive en annen underklasse av Sekvensgenerator bruker Observere klasse:

offentlig klasse SequenceGeneratorUsingMonitor utvider SequenceGenerator {private Monitor mutex = new Monitor (); @Override public int getNextSequence () {mutex.enter (); prøv {return super.getNextSequence (); } til slutt {mutex.leave (); }}}

8. Konklusjon

I denne opplæringen har vi sett på konseptet med en mutex. Vi har også sett de forskjellige måtene å implementere det på Java.

Som alltid er den komplette kildekoden til kodeeksemplene som brukes i denne opplæringen, tilgjengelig på GitHub.