Gids voor java.util.concurrent.Locks

1. Overzicht

Simpel gezegd, een slot is een flexibeler en geavanceerder schroefdraadsynchronisatiemechanisme dan de standaard gesynchroniseerd blok.

De Slot interface bestaat al sinds Java 1.5. Het is gedefinieerd in de java.util.concurrent.lock pakket en het biedt uitgebreide bewerkingen voor vergrendeling.

In dit artikel zullen we verschillende implementaties van het Slot interface en hun toepassingen.

2. Verschillen tussen vergrendeld en gesynchroniseerd blok

Er zijn enkele verschillen tussen het gebruik van gesynchroniseerd blok en gebruiken Slot API's:

  • EEN gesynchroniseerdblok is volledig vervat in een methode - we kunnen hebben Slot API's slot() en ontgrendelen() werking in afzonderlijke methoden
  • Net zogesynchroniseerd blok ondersteunt de eerlijkheid niet, elke draad kan het slot krijgen zodra het is vrijgegeven, er kan geen voorkeur worden gespecificeerd. We kunnen eerlijkheid bereiken binnen de Slot API's door het eerlijkheid eigendom. Het zorgt ervoor dat de langst wachtende thread toegang krijgt tot het slot
  • Een thread wordt geblokkeerd als deze geen toegang kan krijgen tot het gesynchroniseerde blok. De Slot API biedt tryLock () methode. De draad krijgt alleen vergrendeling als deze beschikbaar is en niet door een andere draad wordt vastgehouden. Dit vermindert de blokkeringstijd van de draad die op de vergrendeling wacht
  • Een thread die zich in de "wachtende" status bevindt om toegang te krijgen tot gesynchroniseerd blok, kan niet worden onderbroken. De Slot API biedt een methode lockInterruptibly () die kan worden gebruikt om de draad te onderbreken wanneer deze op het slot wacht

3. Slot API

Laten we eens kijken naar de methoden in het Slot koppel:

  • ongeldig slot ()verkrijg het slot als het beschikbaar is; als het slot niet beschikbaar is, wordt een schroefdraad geblokkeerd totdat het slot wordt vrijgegeven
  • leegte lockInterruptibly () - dit is vergelijkbaar met de slot(), maar het staat toe dat de geblokkeerde thread wordt onderbroken en de uitvoering hervat door middel van een throw java.lang.InterruptedException
  • boolean tryLock ()- dit is een niet-blokkerende versie van slot() methode; het probeert het slot onmiddellijk te verkrijgen, retourneert true als het vergrendelen is gelukt
  • boolean tryLock (lange time-out, TimeUnit timeUnit)dit is vergelijkbaar met tryLock (), behalve dat het de opgegeven time-out wacht voordat het probeert om het Slot
  • leegte ontgrendelen() - ontgrendelt het Slot voorbeeld

Een vergrendelde instantie moet altijd worden ontgrendeld om een ​​impasse te voorkomen. Een aanbevolen codeblok om het slot te gebruiken moet een proberen te vangen en Tenslotte blok:

Vergrendelingsslot = ...; lock.lock (); probeer {// toegang tot de gedeelde bron} tot slot {lock.unlock (); }

Naast de Slot koppel, we hebben een ReadWriteLock interface die een paar vergrendelingen onderhoudt, een voor alleen-lezen bewerkingen en een voor de schrijfbewerking. De leesvergrendeling kan tegelijkertijd door meerdere threads worden vastgehouden, zolang er niet wordt geschreven.

ReadWriteLock verklaart methoden om lees- of schrijfvergrendelingen te verkrijgen:

  • Lock readLock ()geeft het slot terug dat wordt gebruikt voor het lezen
  • Lock writeLock () - geeft het slot terug dat is gebruikt om te schrijven

4. Implementaties vergrendelen

4.1. ReentrantLock

ReentrantLock class implementeert het Slot koppel. Het biedt dezelfde concurrency en geheugensemantiek als de impliciete monitorvergrendeling waartoe toegang wordt verkregen via gesynchroniseerd methoden en verklaringen, met uitgebreide mogelijkheden.

Laten we eens kijken, hoe we het kunnen gebruiken ReenrtantLock voor synchronisatie:

openbare klasse SharedObject {// ... ReentrantLock lock = nieuwe ReentrantLock (); int teller = 0; public void perform () {lock.lock (); probeer {// Kritieke sectie hier count ++; } eindelijk {lock.unlock (); }} // ...}

We moeten ervoor zorgen dat we het slot() en de ontgrendelen() roept in de probeer het eindelijk blok om impassesituaties te vermijden.

Laten we eens kijken hoe de tryLock () werken:

public void performTryLock () {// ... boolean isLockAcquired = lock.tryLock (1, TimeUnit.SECONDS); if (isLockAcquired) {probeer {// Critical section here} eindelijk {lock.unlock (); }} // ...} 

In dit geval roept de thread tryLock (), wacht een seconde en stopt met wachten als het slot niet beschikbaar is.

4.2. ReentrantReadWriteLock

ReentrantReadWriteLock class implementeert het ReadWriteLock koppel.

Laten we de regels bekijken voor het verkrijgen van het ReadLock of WriteLock door een draad:

  • Lees Lock - als geen enkele thread de schrijfvergrendeling heeft verkregen of ervoor heeft aangevraagd, kunnen meerdere threads de leesvergrendeling verkrijgen
  • Schrijf Lock - als er geen threads lezen of schrijven, kan slechts één thread de schrijfvergrendeling verkrijgen

Laten we eens kijken hoe we gebruik kunnen maken van de ReadWriteLock:

openbare klasse SynchronizedHashMapWithReadWriteLock {Map syncHashMap = nieuwe HashMap (); ReadWriteLock lock = nieuwe ReentrantReadWriteLock (); // ... Lock writeLock = lock.writeLock (); openbare ongeldige put (String-sleutel, String-waarde) {probeer {writeLock.lock (); syncHashMap.put (sleutel, waarde); } eindelijk {writeLock.unlock (); }} ... public String remove (String key) {try {writeLock.lock (); return syncHashMap.remove (sleutel); } eindelijk {writeLock.unlock (); }} // ...}

Voor beide schrijfmethoden moeten we de kritieke sectie omringen met de schrijfvergrendeling, slechts één thread kan er toegang toe krijgen:

Lock readLock = lock.readLock (); // ... public String get (String-sleutel) {probeer {readLock.lock (); return syncHashMap.get (sleutel); } eindelijk {readLock.unlock (); }} public boolean containsKey (String-sleutel) {probeer {readLock.lock (); return syncHashMap.containsKey (sleutel); } eindelijk {readLock.unlock (); }}

Voor beide leesmethoden moeten we de kritieke sectie omringen met de leesvergrendeling. Meerdere threads kunnen toegang krijgen tot deze sectie als er geen schrijfbewerking aan de gang is.

4.3. StampedLock

StampedLock wordt geïntroduceerd in Java 8. Het ondersteunt ook zowel lees- als schrijfvergrendelingen. Lock-acquisitiemethoden retourneren echter een stempel die wordt gebruikt om een ​​slot te ontgrendelen of om te controleren of het slot nog steeds geldig is:

openbare klasse StampedLockDemo {Map map = new HashMap (); private StampedLock lock = nieuwe StampedLock (); openbare ongeldige put (String-sleutel, String-waarde) {lange stempel = lock.writeLock (); probeer {map.put (sleutel, waarde); } eindelijk {lock.unlockWrite (stempel); }} public String get (String key) gooit InterruptedException {long stamp = lock.readLock (); probeer {return map.get (key); } eindelijk {lock.unlockRead (stempel); }}}

Een ander kenmerk van StampedLock is een optimistische vergrendeling. Meestal hoeven leesbewerkingen niet te wachten op voltooiing van de schrijfbewerking en als gevolg hiervan is de volwaardige leesvergrendeling niet vereist.

In plaats daarvan kunnen we upgraden om lock te lezen:

openbare String readWithOptimisticLock (String key) {long stamp = lock.tryOptimisticRead (); Stringwaarde = map.get (key); if (! lock.validate (stempel)) {stempel = lock.readLock (); probeer {return map.get (key); } eindelijk {lock.unlock (stempel); } } winstwaarde; }

5. Werken met Voorwaarden

De Staat class biedt de mogelijkheid voor een thread om te wachten tot een bepaalde conditie optreedt tijdens het uitvoeren van de kritieke sectie.

Dit kan gebeuren wanneer een thread toegang krijgt tot de kritieke sectie, maar niet de noodzakelijke voorwaarde heeft om de bewerking uit te voeren. Een lezer-thread kan bijvoorbeeld toegang krijgen tot het slot van een gedeelde wachtrij, die nog steeds geen gegevens heeft om te consumeren.

Traditioneel biedt Java wait (), meld () en meldAll () methoden voor onderlinge communicatie van threads. Voorwaarden hebben vergelijkbare mechanismen, maar daarnaast kunnen we meerdere voorwaarden specificeren:

openbare klasse ReentrantLockWithCondition {Stack stack = new Stack (); int CAPACITEIT = 5; ReentrantLock lock = nieuwe ReentrantLock (); Conditie stackEmptyCondition = lock.newCondition (); Conditie stackFullCondition = lock.newCondition (); public void pushToStack (String-item) {probeer {lock.lock (); while (stack.size () == CAPACITY) {stackFullCondition.await (); } stack.push (item); stackEmptyCondition.signalAll (); } eindelijk {lock.unlock (); }} public String popFromStack () {probeer {lock.lock (); while (stack.size () == 0) {stackEmptyCondition.await (); } return stack.pop (); } eindelijk {stackFullCondition.signalAll (); afsluiten openen(); }}}

6. Conclusie

In dit artikel hebben we verschillende implementaties van de Slot interface en het nieuw geïntroduceerde StampedLock klasse. We hebben ook onderzocht hoe we gebruik kunnen maken van de Staat klasse om met meerdere voorwaarden te werken.

De volledige code voor deze tutorial is beschikbaar op GitHub.