Ръководство за java.util.concurrent.Locks

1. Общ преглед

Най-просто казано, ключалката е по-гъвкав и усъвършенстван механизъм за синхронизация на нишки от стандартния синхронизиран блок.

Интерфейсът Lock съществува от Java 1.5. Той е дефиниран в пакета java.util.concurrent.lock и осигурява обширни операции за заключване.

В тази статия ще разгледаме различни реализации на интерфейса Lock и техните приложения.

2. Разлики между заключване и синхронизиран блок

Има няколко разлики между използването на синхронизиран блок и използването на Lock API:

  • А синхронизирано блок е напълно съдържащи се в рамките на метод - ние можем да имаме Lock API за заключване () и отключване () работа в отделни методи
  • А и ynchronized блок не поддържа справедливостта, всяка нишка може да придобие ключалката веднъж освободен, може да бъде определен не предпочитание. Можем да постигнем честност в Lock API, като посочим свойството на справедливостта . Той гарантира, че най-дълго чакащата нишка има достъп до ключалката
  • Нишката се блокира, ако не може да получи достъп до синхронизирания блок . The Lock API осигурява tryLock () метод. Нишката придобива заключване само ако е налична и не се задържа от друга нишка. Това намалява времето за блокиране на нишката в очакване на заключването
  • Нишка, която е в „чакащо“ състояние за придобиване на достъп до синхронизиран блок , не може да бъде прекъсната. The Lock API осигурява метод lockInterruptibly () , който може да се използва, за да се прекъсне нишката, когато е в очакване на ключалката

3. API за заключване

Нека да разгледаме методите в интерфейса Lock :

  • void lock () - придобийте ключалката, ако е налична; ако заключването не е налично, нишката се блокира, докато заключването бъде освободено
  • void lockInterruptibly () - това е подобно на lock (), но позволява блокираната нишка да бъде прекъсната и да възобнови изпълнението чрез хвърлен java.lang.InterruptedException
  • boolean tryLock () - това е неблокираща версия наметода lock () ; той се опитва да получи ключалката незабавно, връща true, ако заключването успее
  • boolean tryLock (дълго време за изчакване, TimeUnit timeUnit) - това е подобно на tryLock (), с изключение на това , че изчаква даденото време за изчакване, преди да се откаже от опита за придобиване на Lock
  • void unlock () - отключва екземпляра на Lock

Заключен екземпляр винаги трябва да бъде отключен, за да се избегне блокиране. Препоръчителен кодов блок за използване на ключалката трябва да съдържа опит / улавяне и накрая блок:

Lock lock = ...; lock.lock(); try { // access to the shared resource } finally { lock.unlock(); }

В допълнение към интерфейса Lock , имаме интерфейс ReadWriteLock, който поддържа двойка брави, една за операции само за четене и една за операцията за запис. Заключването за четене може да се държи едновременно от множество нишки, докато няма запис.

ReadWriteLock декларира методи за придобиване на ключалки за четене или запис:

  • Lock readLock () - връща заключването, което се използва за четене
  • Lock writeLock () - връща заключването, което се използва за писане

4. Заключване на изпълнението

4.1. ReentrantLock

Класът ReentrantLock реализира Lock интерфейса. Той предлага същата паралелност и семантика на паметта, както имплицитното заключване на монитора, достъпно чрез синхронизирани методи и изрази, с разширени възможности.

Нека видим как можем да използваме ReenrtantLock за синхронизация:

public class SharedObject { //... ReentrantLock lock = new ReentrantLock(); int counter = 0; public void perform() { lock.lock(); try { // Critical section here count++; } finally { lock.unlock(); } } //... }

Трябва да се уверим, че обвиваме заключването () и отключването () в блока try- final, за да избегнем ситуациите на блокиране.

Нека да видим как работи tryLock () :

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

В този случай нишката, извикваща tryLock (), ще изчака една секунда и ще се откаже от чакането, ако заключването не е налично.

4.2. ReentrantReadWriteLock

Класът ReentrantReadWriteLock реализира интерфейса ReadWriteLock .

Нека да видим правила за придобиване на ReadLock или WriteLock чрез нишка:

  • Заключване за четене - ако нито една нишка не е придобила заключването за запис или не е поискала за нея, тогава множество нишки могат да придобият заключването за четене
  • Заключване на запис - ако нито една нишка не чете или записва, тогава само една нишка може да придобие заключване за запис

Нека да видим как да използваме ReadWriteLock :

public class SynchronizedHashMapWithReadWriteLock { Map syncHashMap = new HashMap(); ReadWriteLock lock = new ReentrantReadWriteLock(); // ... Lock writeLock = lock.writeLock(); public void put(String key, String value) { try { writeLock.lock(); syncHashMap.put(key, value); } finally { writeLock.unlock(); } } ... public String remove(String key){ try { writeLock.lock(); return syncHashMap.remove(key); } finally { writeLock.unlock(); } } //... }

И за двата метода на запис трябва да обградим критичната секция със заключване за запис, само една нишка може да получи достъп до нея:

Lock readLock = lock.readLock(); //... public String get(String key){ try { readLock.lock(); return syncHashMap.get(key); } finally { readLock.unlock(); } } public boolean containsKey(String key) { try { readLock.lock(); return syncHashMap.containsKey(key); } finally { readLock.unlock(); } }

И за двата метода за четене трябва да обградим критичната секция със заключване за четене. Няколко нишки могат да получат достъп до този раздел, ако не е в ход операция по запис.

4.3. StampedLock

StampedLock е представен в Java 8. Той също така поддържа заключване за четене и запис. Методите за придобиване на ключалки обаче връщат печат, който се използва за освобождаване на ключалка или за проверка дали заключването все още е валидно:

public class StampedLockDemo { Map map = new HashMap(); private StampedLock lock = new StampedLock(); public void put(String key, String value){ long stamp = lock.writeLock(); try { map.put(key, value); } finally { lock.unlockWrite(stamp); } } public String get(String key) throws InterruptedException { long stamp = lock.readLock(); try { return map.get(key); } finally { lock.unlockRead(stamp); } } }

Друга функция, предоставена от StampedLock, е оптимистично заключване. По-голямата част от операциите за четене на време не трябва да чакат завършването на операцията за запис и в резултат на това не се изисква пълноценното заключване за четене.

Вместо това можем да надстроим, за да четем заключване:

public String readWithOptimisticLock(String key) { long stamp = lock.tryOptimisticRead(); String value = map.get(key); if(!lock.validate(stamp)) { stamp = lock.readLock(); try { return map.get(key); } finally { lock.unlock(stamp); } } return value; }

5. Working With Conditions

The Condition class provides the ability for a thread to wait for some condition to occur while executing the critical section.

This can occur when a thread acquires the access to the critical section but doesn't have the necessary condition to perform its operation. For example, a reader thread can get access to the lock of a shared queue, which still doesn't have any data to consume.

Traditionally Java provides wait(), notify() and notifyAll() methods for thread intercommunication. Conditions have similar mechanisms, but in addition, we can specify multiple conditions:

public class ReentrantLockWithCondition { Stack stack = new Stack(); int CAPACITY = 5; ReentrantLock lock = new ReentrantLock(); Condition stackEmptyCondition = lock.newCondition(); Condition stackFullCondition = lock.newCondition(); public void pushToStack(String item){ try { lock.lock(); while(stack.size() == CAPACITY) { stackFullCondition.await(); } stack.push(item); stackEmptyCondition.signalAll(); } finally { lock.unlock(); } } public String popFromStack() { try { lock.lock(); while(stack.size() == 0) { stackEmptyCondition.await(); } return stack.pop(); } finally { stackFullCondition.signalAll(); lock.unlock(); } } }

6. Conclusion

В тази статия видяхме различни реализации на интерфейса Lock и наскоро въведения клас StampedLock . Също така проучихме как можем да използваме класа Condition за работа с множество условия.

Пълният код за този урок е достъпен в GitHub.