Guide til java.util.concurrent.Locks

1. Oversikt

Enkelt sagt, en lås er en mer fleksibel og sofistikert trådsynkroniseringsmekanisme enn standarden synkronisert blokkere.

De Låse grensesnittet har eksistert siden Java 1.5. Det er definert inne i java.util.concurrent.lock pakken, og den gir omfattende operasjoner for låsing.

I denne artikkelen vil vi utforske forskjellige implementeringer av Låse grensesnitt og deres applikasjoner.

2. Forskjeller mellom lås og synkronisert blokk

Det er få forskjeller mellom bruken av synkronisert blokkere og bruk Låse APIer:

  • EN synkronisertblokkere er fullstendig inneholdt i en metode - vi kan ha Låse API-er låse() og låse opp() drift i separate metoder
  • Somynkronisert blokk støtter ikke rettferdighet, enhver tråd kan skaffe seg låsen når den er utgitt, ingen preferanser kan spesifiseres. Vi kan oppnå rettferdighet innen Låse APIer ved å spesifisere rettferdighet eiendom. Det sørger for at lengste ventetråd får tilgang til låsen
  • En tråd blir blokkert hvis den ikke kan få tilgang til det synkroniserte blokkere. De Låse API gir tryLock () metode. Tråden får bare lås hvis den er tilgjengelig og ikke holdes av noen annen tråd. Dette reduserer blokkeringstiden for tråden som venter på låsen
  • En tråd som er i "ventende" tilstand for å skaffe tilgang til synkronisert blokk, kan ikke avbrytes. De Låse API gir en metode lockInterruptibly () som kan brukes til å avbryte tråden når den venter på låsen

3. Låse API

La oss ta en titt på metodene i Låse grensesnitt:

  • ugyldig lås ()skaffe deg låsen hvis den er tilgjengelig; hvis låsen ikke er tilgjengelig, blir en tråd blokkert til låsen frigjøres
  • void lockInterruptibly () - dette ligner på låse(), men det gjør at den blokkerte tråden kan avbrytes og gjenoppta kjøringen gjennom et kast java.lang.InterruptedException
  • boolsk tryLock ()- dette er en ikke-blokkerende versjon av låse() metode; den prøver å skaffe seg låsen med en gang, og returner sann hvis låsingen lykkes
  • boolsk tryLock (lang tidsavbrudd, TimeUnit timeUnit)dette ligner på tryLock (), bortsett fra at den avventer gitt tidsavbrudd før den gir opp å prøve å skaffe seg Låse
  • tomrom låse opp() - låser opp Låse forekomst

En låst forekomst skal alltid låses opp for å unngå fastlåste forhold. En anbefalt kodeblokk for å bruke låsen skal inneholde en prøve / fange og endelig blokkere:

Låselås = ...; lock.lock (); prøv {// tilgang til den delte ressursen} til slutt {lock.unlock (); }

I tillegg til det Låse grensesnitt, vi har en ReadWriteLock grensesnitt som opprettholder et par låser, en for skrivebeskyttet operasjon og en for skriveoperasjonen. Leselåsen kan holdes samtidig av flere tråder så lenge det ikke er noe å skrive.

ReadWriteLock erklærer metoder for å skaffe lese- eller skrivelåser:

  • Lås readLock ()returnerer låsen som brukes til lesing
  • Lås writeLock () - returnerer låsen som brukes til skriving

4. Lås implementeringer

4.1. ReentrantLock

ReentrantLock klasse implementerer Låse grensesnitt. Den tilbyr samme samtidighet og minnesemantikk, som den implisitte skjermlåsen som du bruker synkronisert metoder og uttalelser, med utvidede muligheter.

La oss se hvordan vi kan bruke ReenrtantLås for synkronisering:

offentlig klasse SharedObject {// ... ReentrantLock lock = new ReentrantLock (); int teller = 0; offentlig ugyldig utføre () {lock.lock (); prøv {// Kritisk del her tell ++; } til slutt {lock.unlock (); }} // ...}

Vi må sørge for at vi pakker inn låse() og låse opp() ringer i prøv-endelig blokker for å unngå fastlåste situasjoner.

La oss se hvordan tryLock () virker:

offentlig ugyldig performTryLock () {// ... boolsk isLockAcquired = lock.tryLock (1, TimeUnit.SECONDS); hvis (isLockAcquired) {prøv {// Kritisk seksjon her} endelig {lock.unlock (); }} // ...} 

I dette tilfellet ringer tråden tryLock (), vil vente i ett sekund og vil gi opp ventetiden hvis låsen ikke er tilgjengelig.

4.2. ReentrantReadWriteLock

ReentrantReadWriteLock klasse implementerer ReadWriteLock grensesnitt.

La oss se regler for anskaffelse av ReadLock eller SkrivLås av en tråd:

  • Les Lås - hvis ingen tråder anskaffet skrivelåsen eller ba om det, kan flere tråder skaffe leselåsen
  • Skriv lås - hvis ingen tråder leser eller skriver, kan bare en tråd få skrivelåsen

La oss se hvordan vi kan bruke ReadWriteLock:

offentlig klasse SynchronizedHashMapWithReadWriteLock {Map syncHashMap = new HashMap (); ReadWriteLock-lås = ny ReentrantReadWriteLock (); // ... Lås writeLock = lock.writeLock (); public void put (String key, String value) {try {writeLock.lock (); syncHashMap.put (nøkkel, verdi); } til slutt {writeLock.unlock (); }} ... offentlig streng fjern (strengnøkkel) {prøv {writeLock.lock (); return syncHashMap.remove (nøkkel); } til slutt {writeLock.unlock (); }} // ...}

For begge skrivemetodene trenger vi å omgir den kritiske delen med skrivelåsen, bare en tråd kan få tilgang til den:

Lås readLock = lock.readLock (); // ... public String get (String key) {try {readLock.lock (); returner syncHashMap.get (nøkkel); } til slutt {readLock.unlock (); }} offentlig boolsk inneholderKey (strengnøkkel) {prøv {readLock.lock (); return syncHashMap.containsKey (nøkkel); } til slutt {readLock.unlock (); }}

For begge lesemetodene må vi omgjøre den kritiske delen med leselåsen. Flere tråder kan få tilgang til denne delen hvis det ikke pågår noen skriveoperasjoner.

4.3. StampedLock

StampedLock er introdusert i Java 8. Den støtter også både lese- og skrivelåser. Metoder for låseanskaffelse returnerer imidlertid et stempel som brukes til å frigjøre en lås eller for å sjekke om låsen fortsatt er gyldig:

offentlig klasse StampedLockDemo {Map map = new HashMap (); privat StampedLock-lås = ny StampedLock (); public void put (String key, String value) {long stamp = lock.writeLock (); prøv {map.put (nøkkel, verdi); } til slutt {lock.unlockWrite (stempel); }} offentlig String get (strengnøkkel) kaster InterruptedException {long stamp = lock.readLock (); prøv {return map.get (key); } til slutt {lock.unlockRead (stempel); }}}

En annen funksjon levert av StampedLock er optimistisk låsing. Det meste av tiden lesoperasjoner trenger ikke å vente på at skrivoperasjonen er fullført, og som et resultat av dette er ikke den fullverdige leselåsen nødvendig.

I stedet kan vi oppgradere til leselås:

public String readWithOptimisticLock (String key) {long stamp = lock.tryOptimisticRead (); Strengverdi = map.get (nøkkel); hvis (! lock.validate (stamp)) {stamp = lock.readLock (); prøv {return map.get (key); } til slutt {lock.unlock (stamp); }} returverdi; }

5. Arbeide med Forhold

De Tilstand klasse gir muligheten for en tråd å vente på at en tilstand skal oppstå mens den kritiske delen utføres.

Dette kan oppstå når en tråd får tilgang til den kritiske delen, men ikke har den nødvendige forutsetningen for å utføre operasjonen. For eksempel kan en lesertråd få tilgang til låsen til en delt kø, som fremdeles ikke har noen data å konsumere.

Tradisjonelt gir Java vent (), varsle () og varsleAll () metoder for trådkommunikasjon. Forhold har lignende mekanismer, men i tillegg kan vi spesifisere flere forhold:

offentlig klasse ReentrantLockWithCondition {Stack stack = new Stack (); int KAPASITET = 5; ReentrantLock-lås = ny ReentrantLock (); Tilstand stackEmptyCondition = lock.newCondition (); Tilstand stackFullCondition = lock.newCondition (); offentlig ugyldig pushToStack (strengelement) {prøv {lock.lock (); mens (stack.size () == CAPACITY) {stackFullCondition.await (); } stack.push (element); stackEmptyCondition.signalAll (); } til slutt {lock.unlock (); }} offentlig streng popFromStack () {prøv {lock.lock (); mens (stack.size () == 0) {stackEmptyCondition.await (); } returner stack.pop (); } til slutt {stackFullCondition.signalAll (); Lås åpne(); }}}

6. Konklusjon

I denne artikkelen har vi sett forskjellige implementeringer av Låse grensesnitt og det nylig introduserte StampedLock klasse. Vi undersøkte også hvordan vi kan bruke Tilstand klasse for å jobbe med flere forhold.

Den komplette koden for denne opplæringen er tilgjengelig på GitHub.


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