1. Обзор
Проще говоря, блокировка-это более гибкий и сложный механизм синхронизации потоков, чем стандартный блок synchronized .
Интерфейс Lock существует с Java 1.5. Он определен в файле java.util.concurrent.пакет lock и он обеспечивает обширные операции по блокировке.
В этой статье мы рассмотрим различные реализации интерфейса Lock и их приложения.
2. Различия между блокировкой и Синхронизированным блоком
Существует несколько различий между использованием synchronized block и использованием Lock API:
- /| синхронизированный блок полностью содержится в методе – у нас может быть Блокировка API блокировка() и разблокировка() операция в отдельных методах Поскольку
- synchronized block не поддерживает справедливость, любой поток может получить блокировку после освобождения, никакие предпочтения не могут быть указаны. Мы можем добиться справедливости в API-интерфейсах Lock , указав свойство fair /. Это гарантирует, что самый длинный ожидающий поток получит доступ к блокировке Поток блокируется, если он не может получить доступ к синхронизированному блоку
- . API Lock предоставляет метод tryLock () . Поток получает блокировку только в том случае, если он доступен и не удерживается каким-либо другим потоком. Это сокращает время блокировки потока, ожидающего блокировки Поток , находящийся в состоянии “ожидания” для получения доступа к синхронизированному блоку
- , не может быть прерван. API Lock предоставляет метод lockInterruptibly () , который можно использовать для прерывания потока, когда он ожидает блокировки
3. Блокировка API
Давайте взглянем на методы в интерфейсе Lock :
- void lock() – получить блокировку, если она доступна; если блокировка недоступна, поток блокируется до тех пор, пока блокировка не будет снята
- void lockInterruptibly() – это похоже на lock(), но это позволяет прервать заблокированный поток и возобновить выполнение через брошенный java.lang.Исключение InterruptedException
- boolean tryLock() – это неблокирующая версия метода lock () ; он пытается немедленно получить блокировку, возвращает true, если блокировка удалась
- логический tryLock(длинный тайм – аут, TimeUnit TimeUnit) – это похоже на tryLock(), за исключением того, что он ждет заданного тайм-аута, прежде чем отказаться от попыток получить Блокировку
- void unlock() – разблокирует экземпляр Lock
Заблокированный экземпляр всегда должен быть разблокирован, чтобы избежать состояния взаимоблокировки. Рекомендуемый блок кода для использования блокировки должен содержать try/catch и finally блок:
Lock lock = ...; lock.lock(); try { // access to the shared resource } finally { lock.unlock(); }
В дополнение к интерфейсу Lock , у нас есть интерфейс ReadWriteLock , который поддерживает пару блокировок, одну для операций только для чтения и одну для операции записи. Блокировка чтения может одновременно удерживаться несколькими потоками до тех пор, пока нет записи.
ReadWriteLock объявляет методы для получения блокировок чтения или записи:
- Lock readLock() – возвращает блокировку, используемую для чтения
- Lock writeLock() – возвращает блокировку, используемую для записи
4. Реализация Блокировки
4.1. Повторная блокировка
Класс ReentrantLock реализует интерфейс Lock . Он предлагает ту же семантику параллелизма и памяти, что и неявная блокировка монитора, доступ к которой осуществляется с помощью методов и операторов synchronized , с расширенными возможностями.
Давайте посмотрим, как мы можем использовать Reentrantlock для синхронизации:
public class SharedObject { //... ReentrantLock lock = new ReentrantLock(); int counter = 0; public void perform() { lock.lock(); try { // Critical section here count++; } finally { lock.unlock(); } } //... }
Нам нужно убедиться, что мы оборачиваем вызовы lock () и unlock() в блок try-finally , чтобы избежать тупиковых ситуаций.
Давайте посмотрим, как работает 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 потоком:
- Блокировка чтения – если ни один поток не получил блокировку записи или не запросил ее, то несколько потоков могут получить блокировку чтения
- Блокировка записи – если ни один поток не читает или не записывает, то только один поток может получить блокировку записи
Давайте посмотрим, как использовать блокировку чтения и записи :
public class SynchronizedHashMapWithReadWriteLock { MapsyncHashMap = 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 { Mapmap = 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. Работа С Условиями
Класс Condition предоставляет потоку возможность дождаться выполнения некоторого условия во время выполнения критического раздела.
Это может произойти, когда поток получает доступ к критическому разделу, но не имеет необходимых условий для выполнения своей операции. Например, поток чтения может получить доступ к блокировке общей очереди, в которой по-прежнему нет данных для использования.
Традиционно Java предоставляет wait(), notify() и notifyAll() методы для взаимодействия потоков. Условия имеют схожие механизмы, но, кроме того, мы можем указать несколько условий:
public class ReentrantLockWithCondition { Stackstack = 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. Заключение
В этой статье мы рассмотрели различные реализации интерфейса Lock и недавно введенного класса StampedLock . Мы также изучили, как мы можем использовать класс Condition для работы с несколькими условиями.
Полный код этого руководства доступен на GitHub .