Vejledning til java.util.concurrent.Locks

1. Oversigt

Kort sagt, en lås er en mere fleksibel og sofistikeret trådsynkroniseringsmekanisme end standarden synkroniseret blok.

Det Låse interface har eksisteret siden Java 1.5. Det er defineret inde i java.util.concurrent.lock pakke, og det giver omfattende operationer til låsning.

I denne artikel undersøger vi forskellige implementeringer af Låse interface og deres applikationer.

2. Forskelle mellem lås og synkroniseret blok

Der er få forskelle mellem brugen af ​​synkroniseret blok og brug Låse API'er:

  • EN synkroniseretblok er fuldt indeholdt i en metode - vi kan have Låse API'er låse() og låse op () drift i separate metoder
  • Somynkroniseret blok understøtter ikke retfærdighed, enhver tråd kan erhverve låsen, når den først er frigivet, ingen præference kan specificeres. Vi kan opnå retfærdighed inden for Låse API'er ved at specificere retfærdighed ejendom. Det sørger for, at længst ventende tråd får adgang til låsen
  • En tråd bliver blokeret, hvis den ikke kan få adgang til den synkroniserede blok. Det Låse API leverer tryLock () metode. Tråden erhverver kun lås, hvis den er tilgængelig og ikke holdes af nogen anden tråd. Dette reducerer blokeringstiden for tråd, der venter på låsen
  • En tråd, der er i "ventende" tilstand for at få adgang til synkroniseret blok, kan ikke afbrydes. Det Låse API giver en metode lockInterruptibly () som kan bruges til at afbryde tråden, når den venter på låsen

3. Låse API

Lad os se på metoderne i Låse grænseflade:

  • tomrumslås ()erhverve låsen, hvis den er tilgængelig; hvis låsen ikke er tilgængelig, bliver en tråd blokeret, indtil låsen frigøres
  • tomrumslås Afbrydelig () - dette svarer til låse(), men det gør det muligt at afbryde den blokerede tråd og genoptage udførelsen gennem et kast java.lang.InterruptedException
  • boolsk tryLock ()- dette er en ikke-blokerende version af låse() metode; det forsøger at erhverve låsen med det samme, returner sand, hvis låsning lykkes
  • boolsk tryLock (lang timeout, TimeUnit timeUnit)dette svarer til tryLock (), bortset fra at den afventer den givne timeout, før den opgiver at prøve at erhverve Låse
  • ugyldig låse op () - låser op for Låse eksempel

En låst forekomst skal altid låses op for at undgå blokering. En anbefalet kodeblok til brug af låsen skal indeholde en prøv / fange og langt om længe blok:

Låselås = ...; lock.lock (); prøv {// adgang til den delte ressource} endelig {lock.unlock (); }

Ud over Låse interface, vi har en ReadWriteLock interface, der opretholder et par låse, en til skrivebeskyttet handling, og en til skriveoperationen. Læselåsen kan holdes samtidigt af flere tråde, så længe der ikke er nogen skrivning.

ReadWriteLock erklærer metoder til at erhverve læse- eller skrivelåse:

  • Lås readLock ()returnerer den lås, der bruges til læsning
  • Lås skriveLås () - returnerer den lås, der bruges til at skrive

4. Lås implementeringer

4.1. ReentrantLock

ReentrantLock klasse implementerer Låse interface. Det tilbyder den samme samtidighed og hukommelsessemantik, som den implicitte skærmlås tilgås ved hjælp af synkroniseret metoder og udsagn med udvidede muligheder.

Lad os se, hvordan vi kan bruge ReenrtantLock for synkronisering:

offentlig klasse SharedObject {// ... ReentrantLock-lås = ny ReentrantLock (); int tæller = 0; offentlig ugyldighed udføre () {lock.lock (); prøv {// Kritisk afsnit her tæller ++; } endelig {lock.unlock (); }} // ...}

Vi er nødt til at sikre, at vi indpakker låse() og låse op () opkald i prøv endelig blokere for at undgå blokeringer.

Lad os se, hvordan tryLock () arbejder:

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

I dette tilfælde ringer tråden tryLock (), venter et sekund og giver op med at vente, hvis låsen ikke er tilgængelig.

4.2. ReentrantReadWriteLock

ReentrantReadWriteLock klasse implementerer ReadWriteLock interface.

Lad os se regler for erhvervelse af ReadLock eller WriteLock ved en tråd:

  • Læs Lås - hvis ingen tråd erhvervede skrivelåsen eller anmodede om det, kan flere tråde erhverve læselåsen
  • Skriv lås - hvis ingen tråde læser eller skriver, kan kun en tråd erhverve skrivelåsen

Lad os se, hvordan vi gør brug af ReadWriteLock:

offentlig klasse SynchronizedHashMapWithReadWriteLock {Map syncHashMap = ny HashMap (); ReadWriteLock-lås = ny ReentrantReadWriteLock (); // ... Lås writeLock = lock.writeLock (); public void put (String key, String value) {try {writeLock.lock (); syncHashMap.put (nøgle, værdi); } endelig {writeLock.unlock (); }} ... offentlig fjernelse af streng (strengnøgle) {prøv {writeLock.lock (); returner syncHashMap.remove (nøgle); } endelig {writeLock.unlock (); }} // ...}

For begge skrivemetoder er vi nødt til at omgive det kritiske afsnit med skrivelåsen, kun en tråd kan få adgang til den:

Lås readLock = lock.readLock (); // ... public String get (String key) {prøv {readLock.lock (); returner syncHashMap.get (nøgle); } endelig {readLock.unlock (); }} offentlig boolsk containKey (strengnøgle) {prøv {readLock.lock (); returner syncHashMap.containsKey (nøgle); } endelig {readLock.unlock (); }}

For begge læsemetoder er vi nødt til at omgive det kritiske afsnit med læselåsen. Flere tråde kan få adgang til dette afsnit, hvis der ikke er nogen skrivehandling.

4.3. StempletLås

StempletLås introduceres i Java 8. Det understøtter også både læse- og skrivelåse. Imidlertid returnerer låseanskaffelsesmetoder et stempel, der bruges til at frigøre en lås eller for at kontrollere, om låsen stadig 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øgle, værdi); } endelig {lock.unlockWrite (stempel); }} offentlig String get (String key) kaster InterruptedException {long stamp = lock.readLock (); prøv {return map.get (nøgle); } endelig {lock.unlockRead (stempel); }}}

En anden funktion leveret af StempletLås er optimistisk låsning. Det meste af tiden læste operationer behøver ikke at vente på afslutning af skrivoperationen, og som et resultat heraf er den fulde læselås ikke nødvendig.

I stedet kan vi opgradere til læselås:

public String readWithOptimisticLock (String key) {long stamp = lock.tryOptimisticRead (); Strengværdi = map.get (nøgle); hvis (! lock.validate (stamp)) {stamp = lock.readLock (); prøv {return map.get (key); } endelig {lock.unlock (stempel); }} returværdi; }

5. Arbejde med Betingelser

Det Tilstand klasse giver en tråd mulighed for at vente på, at en eller anden tilstand skal opstå, mens den kritiske sektion udføres.

Dette kan forekomme, når en tråd får adgang til den kritiske sektion, men ikke har den nødvendige betingelse for at udføre dens operation. For eksempel kan en læsertråd få adgang til låsen af ​​en delt kø, som stadig ikke har nogen data at forbruge.

Traditionelt leverer Java vent (), underret () og notifyAll () metoder til trådkommunikation. Betingelser har lignende mekanismer, men derudover kan vi angive flere betingelser:

offentlig klasse ReentrantLockWithCondition {Stack stack = new Stack (); int KAPACITET = 5; ReentrantLock-lås = ny ReentrantLock (); Tilstand stackEmptyCondition = lock.newCondition (); Betingelse stackFullCondition = lock.newCondition (); public void pushToStack (String item) {prøv {lock.lock (); mens (stack.size () == CAPACITY) {stackFullCondition.await (); } stack.push (element); stackEmptyCondition.signalAll (); } endelig {lock.unlock (); }} offentlig streng popFromStack () {prøv {lock.lock (); mens (stack.size () == 0) {stackEmptyCondition.await (); } returner stack.pop (); } endelig {stackFullCondition.signalAll (); lock.unlock (); }}}

6. Konklusion

I denne artikel har vi set forskellige implementeringer af Låse interface og den nyligt introducerede StempletLås klasse. Vi undersøgte også, hvordan vi kan gøre brug af Tilstand klasse til at arbejde med flere forhold.

Den komplette kode til denne vejledning er tilgængelig på GitHub.