Sprievodca po java.util.concurrent.Locks

1. Prehľad

Jednoducho povedané, zámok je flexibilnejší a sofistikovanejší mechanizmus synchronizácie vlákien ako štandard synchronizované blokovať.

The Zamknúť rozhranie existuje už od Javy 1.5. Je to definované vo vnútri java.util.concurrent.lock balík a poskytuje rozsiahle operácie uzamykania.

V tomto článku sa budeme zaoberať rôznymi implementáciami Zamknúť rozhranie a ich aplikácie.

2. Rozdiely medzi zámkom a synchronizovaným blokom

Medzi použitím synchronizovaného existuje niekoľko rozdielov blokovať a pomocou Zamknúť API:

  • A synchronizovanéblokovať je úplne obsiahnutý v metóde - môžeme mať Zamknúť API zámok () a odomknúť () prevádzka samostatnými metódami
  • A synchronizovaný blok nepodporuje spravodlivosť, akékoľvek vlákno môže získať zámok po jeho uvoľnení, nie je možné určiť nijaké predvoľby. Spravodlivosť môžeme dosiahnuť v rámci Zamknúť API špecifikáciou férovosť nehnuteľnosť. Zaisťuje, aby prístup k zámku dostal najdlhšie čakajúce vlákno
  • Vlákno sa zablokuje, ak nemôže získať prístup k synchronizovanému blokovať. The Zamknúť API poskytuje tryLock () metóda. Vlákno získa zámok, iba ak je k dispozícii a nie je držané iným vláknom. To znižuje čas blokovania vlákna čakajúci na zámok
  • Vlákno, ktoré je v „čakajúcom“ stave na získanie prístupu k synchronizovaný blok, nemožno prerušiť. The Zamknúť API poskytuje metódu lockInterruptibly () ktorým je možné prerušiť vlákno, keď čaká na zámok

3. Zamknúť API

Poďme sa pozrieť na metódy v Zamknúť rozhranie:

  • neplatný zámok ()získajte zámok, ak je k dispozícii; ak zámok nie je k dispozícii, vlákno sa zablokuje, kým sa zámok neuvoľní
  • neplatný zámok prerušiteľný () - toto je podobné ako v zámok (), ale umožňuje prerušenie blokovaného vlákna a obnovenie vykonania vyhodením java.lang.InterruptedException
  • boolean tryLock ()- toto je neblokujúca verzia aplikácie zámok () metóda; pokúsi sa získať zámok okamžite, ak je zámok úspešný, vráti hodnotu true
  • boolean tryLock (long timeout, TimeUnit timeUnit)toto je podobné ako tryLock (), až na to, že počká daný časový limit, kým sa vzdá pokusu o získanie Zamknúť
  • neplatný odomknúť () - odomkne Zamknúť inštancia

Uzamknutá inštancia by mala byť vždy odomknutá, aby nedošlo k zablokovaniu. Odporúčaný blok kódu na použitie zámku by mal obsahovať a Skús chytiť a konečne blok:

Zamknúť zámok = ...; lock.lock (); skúste {// prístup k zdieľanému prostriedku} konečne {lock.unlock (); }

Navyše k Zamknúť rozhranie, máme ReadWriteLock rozhranie, ktoré udržuje dvojicu zámkov, jeden pre operácie iba na čítanie a jeden pre operáciu zápisu. Zámok na čítanie môže byť súčasne držaný viacerými vláknami, pokiaľ nedochádza k zápisu.

ReadWriteLock deklaruje metódy na získanie zámkov na čítanie alebo zápis:

  • Uzamknúť readLock ()vráti zámok, ktorý sa používa na čítanie
  • Uzamknúť writeLock () - vráti zámok, ktorý sa používa na zápis

4. Uzamknite implementácie

4.1. ReentrantLock

ReentrantLock trieda implementuje Zamknúť rozhranie. Ponúka rovnakú sémantiku súbežnosti a pamäte ako implicitný zámok monitora prístupný pomocou synchronizované metódy a vyhlásenia s rozšírenými schopnosťami.

Pozrime sa, ako môžeme využiť ReenrtantLock pre synchronizácia:

verejná trieda SharedObject {// ... ReentrantLock lock = nový ReentrantLock (); int counter = 0; public void perform () {lock.lock (); skúste {// Kritická časť tu count ++; } nakoniec {lock.unlock (); }} // ...}

Musíme sa uistiť, že obalujeme zámok() a odomknúť () volá v skús-konečne blok, aby sa zabránilo situáciám na mŕtvom bode.

Pozrime sa, ako tryLock () Tvorba:

public void perform TryLock () {// ... boolean isLockAcquired = lock.tryLock (1, TimeUnit.SECONDS); if (isLockAcquired) {vyskúšať {// kritická časť tu} konečne {lock.unlock (); }} // ...} 

V tomto prípade vlákno volá tryLock (), počká jednu sekundu a vzdá sa čakania, ak zámok nie je k dispozícii.

4.2. ReentrantReadWriteLock

ReentrantReadWriteLock trieda implementuje ReadWriteLock rozhranie.

Pozrime sa na pravidlá pre získanie ReadLock alebo WriteLock pomocou vlákna:

  • Prečítajte si Lock - ak žiadne vlákno nezískalo zámok pre zápis alebo oň nebolo požiadané, potom môže viac vlákien získať zámok pre čítanie
  • Write Lock - ak žiadne vlákna nečítajú ani nezapisujú, zámku zápisu môže získať iba jedno vlákno

Pozrime sa, ako využiť ReadWriteLock:

verejná trieda SynchronizedHashMapWithReadWriteLock {Map syncHashMap = nový HashMap (); Zámok ReadWriteLock = nový ReentrantReadWriteLock (); // ... Lock writeLock = lock.writeLock (); public void put (kľúč reťazca, hodnota reťazca) {try {writeLock.lock (); syncHashMap.put (kľúč, hodnota); } nakoniec {writeLock.unlock (); }} ... public String remove (String key) {try {writeLock.lock (); návrat syncHashMap.remove (kľúč); } nakoniec {writeLock.unlock (); }} // ...}

Pre obidve metódy zápisu musíme kritickú časť obklopiť zámkom proti zápisu, prístup k nej môže získať iba jedno vlákno:

Lock readLock = lock.readLock (); // ... public String get (reťazcový kľúč) {try {readLock.lock (); návrat syncHashMap.get (kľúč); } nakoniec {readLock.unlock (); }} public boolean containsKey (String key) {try {readLock.lock (); návrat syncHashMap.containsKey (kľúč); } nakoniec {readLock.unlock (); }}

Pri obidvoch metódach čítania musíme kritickú časť obklopiť zámkom na čítanie. Ak neprebieha operácia zápisu, do tejto sekcie môže získať prístup viac vlákien.

4.3. StampedLock

StampedLock je zavedený v prostredí Java 8. Podporuje tiež zámky na čítanie a zápis. Metódy získavania zámku však vrátia pečiatku, ktorá sa používa na uvoľnenie zámku alebo na kontrolu, či je zámok stále platný:

verejná trieda StampedLockDemo {Map map = new HashMap (); súkromný zámok StampedLock = nový StampedLock (); public void put (kľúč reťazca, hodnota reťazca) {dlhá známka = lock.writeLock (); skúste {map.put (kľúč, hodnota); } nakoniec {lock.unlockWrite (známka); }} public String get (String key) hodí InterruptedException {long stamp = lock.readLock (); skus {vratit map.get (kluc); } nakoniec {lock.unlockRead (známka); }}}

Ďalšiu funkciu poskytuje StampedLock je optimistické zamykanie. Väčšinu času operácie čítania nemusíte čakať na dokončenie operácie zápisu a v dôsledku toho sa nevyžaduje plnohodnotný zámok čítania.

Namiesto toho môžeme inovovať na čítanie zámku:

public String readWithOptimisticLock (reťazcový kľúč) {dlhá známka = lock.tryOptimisticRead (); Hodnota reťazca = map.get (kľúč); if (! lock.validate (stamp)) {stamp = lock.readLock (); skus {vratit map.get (kluc); } nakoniec {lock.unlock (známka); }} návratová hodnota; }

5. Spolupráca s Podmienky

The Stav Táto trieda poskytuje vláknu schopnosť čakať na vykonanie kritickej časti, kým nenastane nejaký stav.

To môže nastať, keď vlákno získa prístup do kritickej sekcie, ale nemá potrebné podmienky na vykonanie svojej operácie. Napríklad vlákno čítačky môže získať prístup k zámku zdieľaného frontu, ktorý stále nemá na konzumáciu žiadne údaje.

Tradične poskytuje Java wait (), notify () a notifyAll () metódy pre vzájomnú komunikáciu medzi vláknami. Podmienky majú podobné mechanizmy, ale okrem toho môžeme určiť viac podmienok:

public class ReentrantLockWithCondition {Stack stack = new Stack (); int KAPACITA = 5; Zámok ReentrantLock = nový ReentrantLock (); Podmienka stackEmptyCondition = lock.newCondition (); Stav stackFullCondition = lock.newCondition (); public void pushToStack (reťazec) {try {lock.lock (); while (stack.size () == CAPACITY) {stackFullCondition.await (); } stack.push (položka); stackEmptyCondition.signalAll (); } nakoniec {lock.unlock (); }} public String popFromStack () {try {lock.lock (); while (stack.size () == 0) {stackEmptyCondition.await (); } návrat stack.pop (); } nakoniec {stackFullCondition.signalAll (); lock.unlock (); }}}

6. Záver

V tomto článku sme videli rôzne implementácie Zamknúť rozhranie a novo zavedené StampedLock trieda. Preskúmali sme tiež, ako môžeme využiť Stav triedy pracovať s viacerými podmienkami.

Celý kód tohto tutoriálu je k dispozícii na GitHub.


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