Vodič za java.util.concurrent.Locks

1. Pregled

Jednostavno rečeno, brava je fleksibilniji i sofisticiraniji mehanizam za sinkronizaciju niti od standardne sinkronizirano blok.

The Zaključaj sučelje postoji od Jave 1.5. Definirano je unutar java.util.concurrent.lock paket i pruža opsežne operacije zaključavanja.

U ovom ćemo članku istražiti različite implementacije Zaključaj sučelje i njihove primjene.

2. Razlike između zaključavanja i sinkroniziranog bloka

Malo je razlika između uporabe sinkroniziranih blok i koristeći Zaključaj Apis:

  • A sinkroniziranoblok je u potpunosti sadržan u metodi - možemo imati Zaključaj Apis zaključaj () i otključati() rad odvojenim metodama
  • A ssinkronizirani blok ne podržava pravičnost, bilo koja nit može dobiti bravu nakon što se pusti, ne može se odrediti nikakva prednost. Možemo postići pravednost unutar Zaključaj API-ji specificiranjem poštenje imovine. Osigurava da nit s najdužim čekanjem dobije pristup bravi
  • Nit se blokira ako ne može dobiti pristup sinkroniziranom blok. The Zaključaj API pruža tryLock () metoda. Konac dobiva zaključavanje samo ako je dostupan i ne zadržava ga niti jedna druga nit. To smanjuje vrijeme blokiranja niti koja čeka na bravu
  • Nit koja je u stanju "čekanja" za stjecanje pristupa sinkronizirani blok, ne može se prekinuti. The Zaključaj API pruža metodu lockInruptrubly () koji se može koristiti za prekidanje niti kada čeka zaključavanje

3. Zaključaj API

Pogledajmo metode u Zaključaj sučelje:

  • zaključavanje praznine ()nabavi bravu ako je dostupna; ako brava nije dostupna, nit se blokira dok se brava ne otpusti
  • void lockInruptrubly () - ovo je slično zaključaj (), ali dopušta da se blokirana nit prekine i nastavi izvođenje kroz bačenu java.lang.InterruptedException
  • logička probaLock ()- ovo je neblokirajuća verzija zaključaj () metoda; pokušava odmah dobiti bravu, vratiti true ako zaključavanje uspije
  • logička probaLock (dugo vrijeme čekanja, TimeUnit timeUnit)ovo je slično tryLock (), osim što čeka zadano vremensko ograničenje prije nego što odustane od pokušaja stjecanja Zaključaj
  • poništiti otključati() - otključava Zaključaj primjer

Zaključani primjerak uvijek treba otključati kako bi se izbjeglo stanje mrtve točke. Preporučeni blok koda za upotrebu brave trebao bi sadržavati a pokušaj uhvatiti i konačno blok:

Zaključavanje brave = ...; lock.lock (); isprobajte {// pristup zajedničkom resursu} konačno {lock.unlock (); }

Uz to Zaključaj sučelje, imamo ReadWriteLock sučelje koje održava par brava, jedno za operacije samo za čitanje i jedno za operaciju pisanja. Zaključavanje čitanja može istovremeno držati više niti sve dok nema zapisivanja.

ReadWriteLock izjavljuje metode za stjecanje brava za čitanje ili pisanje:

  • Zaključaj readLock ()vraća bravu koja se koristi za čitanje
  • Zaključaj writeLock () - vraća bravu koja se koristi za pisanje

4. Implementacije zaključavanja

4.1. ReentrantLock

ReentrantLock razred provodi Zaključaj sučelje. Nudi istu istovremenost i semantiku memorije kao implicitna brava monitora kojoj se pristupa sinkronizirano metode i izjave, s proširenim mogućnostima.

Da vidimo, kako se možemo koristiti ReenrtantLock za sinkronizacija:

javna klasa SharedObject {// ... ReentrantLock lock = new ReentrantLock (); int brojač = 0; javna void izvedba () {lock.lock (); probajte {// Kritični odjeljak ovdje count ++; } napokon {lock.unlock (); }} // ...}

Moramo biti sigurni da umotavamo zaključati() i otključati() poziva u probaj-napokon blokirajte kako biste izbjegli mrtve točke.

Da vidimo kako tryLock () djela:

javna praznina performTryLock () {// ... boolean isLockAcquired = lock.tryLock (1, TimeUnit.SECONDS); if (isLockAcquired) {try {// Ovdje je kritični odjeljak} konačno {lock.unlock (); }} // ...} 

U ovom slučaju, nit poziva tryLock (), pričekat će jednu sekundu i odustat će od čekanja ako zaključavanje nije dostupno.

4.2. ReentrantReadWriteLock

ReentrantReadWriteLock razred provodi ReadWriteLock sučelje.

Pogledajmo pravila za stjecanje ReadLock ili WriteLock nitom:

  • Read Lock - ako niti jedna nit nije nabavila blokadu pisanja ili je za nju zatražila, tada više niti može dobiti blokadu čitanja
  • Write Lock - ako niti jedna nit ne čita ili ne piše, onda samo jedna nit može dobiti blokadu upisa

Pogledajmo kako iskoristiti ReadWriteLock:

javna klasa SynchronizedHashMapWithReadWriteLock {Map syncHashMap = new HashMap (); ReadWriteLock zaključavanje = novo ReentrantReadWriteLock (); // ... Zaključaj writeLock = lock.writeLock (); javna praznina (ključ niza, vrijednost niza) {try {writeLock.lock (); syncHashMap.put (ključ, vrijednost); } napokon {writeLock.unlock (); }} ... javni uklanjanje niza (ključ niza) {try {writeLock.lock (); vratiti syncHashMap.remove (ključ); } napokon {writeLock.unlock (); }} // ...}

Za obje metode pisanja moramo kritični odjeljak okružiti zaključavanjem, samo mu jedna nit može pristupiti:

Zaključaj readLock = lock.readLock (); // ... javni String get (String key) {try {readLock.lock (); vratiti syncHashMap.get (ključ); } napokon {readLock.unlock (); }} javni boolean sadržiKey (ključ niza) {try {readLock.lock (); vratiti syncHashMap.containsKey (ključ); } napokon {readLock.unlock (); }}

Za obje metode čitanja moramo kritični odjeljak okružiti bravom za čitanje. Više niti može dobiti pristup ovom odjeljku ako nijedna operacija pisanja nije u tijeku.

4.3. StampedLock

StampedLock uveden je u Javi 8. Također podržava brave za čitanje i pisanje. Međutim, metode pribavljanja brave vraćaju pečat koji se koristi za otpuštanje brave ili za provjeru je li brava još uvijek važeća:

javna klasa StampedLockDemo {Map map = new HashMap (); privatna zaključana StampedLock = nova StampedLock (); javna praznina (ključ niza, vrijednost niza) {long stamp = lock.writeLock (); probajte {map.put (ključ, vrijednost); } napokon {lock.unlockWrite (pečat); }} javni String get (String key) baca InterruptedException {long stamp = lock.readLock (); pokušajte {return map.get (ključ); } napokon {lock.unlockRead (pečat); }}}

Još jedna značajka koju pruža StampedLock je optimistično zaključavanje. Većinu vremena operacije čitanja ne trebaju čekati završetak operacije pisanja, a kao rezultat toga, punopravno zaključavanje čitanja nije potrebno.

Umjesto toga, možemo nadograditi na čitanje zaključavanja:

javni String readWithOptimisticLock (String key) {long stamp = lock.tryOptimisticRead (); Vrijednost niza = map.get (ključ); if (! lock.validate (stamp)) {stamp = lock.readLock (); probajte {return map.get (ključ); } napokon {lock.unlock (pečat); }} povratna vrijednost; }

5. Rad s Uvjeti

The Stanje klasa pruža mogućnost niti da čeka izvršavanje nekog stanja tijekom izvršavanja kritičnog odjeljka.

To se može dogoditi kada nit dobije pristup kritičnom odjeljku, ali nema potrebne uvjete za obavljanje svoje operacije. Na primjer, nit čitača može dobiti pristup zaključavanju zajedničkog reda, koji još uvijek nema podataka za trošenje.

Java tradicionalno nudi wait (), notify () i notifyAll () metode za međusobnu komunikaciju niti. Uvjeti imaju slične mehanizme, ali uz to možemo odrediti više uvjeta:

javna klasa ReentrantLockWithCondition {Stack stack = novi Stack (); int KAPACITET = 5; Zaključavanje ReentrantLock = novo ReentrantLock (); Stanje stackEmptyCondition = lock.newCondition (); Stanje stackFullCondition = lock.newCondition (); javna void pushToStack (String stavka) {try {lock.lock (); while (stack.size () == KAPACITET) {stackFullCondition.await (); } stack.push (stavka); stackEmptyCondition.signalAll (); } napokon {lock.unlock (); }} javni String popFromStack () {try {lock.lock (); while (stack.size () == 0) {stackEmptyCondition.await (); } vratiti stack.pop (); } napokon {stackFullCondition.signalAll (); lock.unlock (); }}}

6. Zaključak

U ovom smo članku vidjeli različite implementacije Zaključaj sučelje i novo uvedeno StampedLock razred. Također smo istražili kako možemo iskoristiti Stanje razred za rad s više uvjeta.

Kompletni kôd ovog vodiča dostupan je na GitHub-u.