ReentrantLock¶
ReentrantLock — это класс в языке программирования Java, реализующий интерфейс Lock и предоставляющий механизм синхронизации потоков с возможностью повторного захвата монитора тем же потоком. Относится к пакету java.util.concurrent.locks, впервые появился в Java 5.0 (Java SE 5) как часть библиотеки параллельных структур, разработанной под руководством Дугласа Ли.
¶Общие сведения
ReentrantLock является альтернативой встроенному синхронизированному блоку (synchronized), но предоставляет более гибкий и расширенный набор возможностей. Класс реализует интерфейс Lock, который определяет базовые операции захвата и освобождения блокировки. Название класса отражает его ключевую особенность — реентерабельность, то есть способность одного и того же потока повторно захватывать уже удерживаемую им блокировку без возникновения взаимоблокировки (deadlock).
В отличие от synchronized, ReentrantLock позволяет явно управлять процессом блокирования: захватывать и освобождать блокировку в разных методах или блоках кода, что невозможно при использовании встроенного монитора, где освобождение происходит автоматически при выходе из синхронизированного блока.
¶Основные характеристики
¶Реентерабельность
Поток, удерживающий блокировку, может вызывать другие методы, которые также захватывают эту же блокировку. При этом счётчик захватов увеличивается, а при каждом освобождении — уменьшается. Блокировка считается освобождённой только тогда, когда счётчик достигает нуля. Это позволяет избежать самоблокировки при рекурсивных вызовах или вызовах взаимозависимых методов.
¶Справедливость
ReentrantLock может быть создан как «справедливый» или «несправедливый». При создании экземпляра конструктору можно передать логический параметр fair. Справедливый режим гарантирует, что блокировка будет передана потоку, ожидающему дольше всех (FIFO-порядок). Несправедливый режим, используемый по умолчанию, допускает «перехват» блокировки новым потоком, что может повысить пропускную способность, но создаёт риск голодания ожидающих потоков.
¶Условные переменные
Класс предоставляет метод newCondition(), который возвращает объект Condition, используемый для организации ожидания и уведомления потоков. Это аналог методов wait(), notify() и notifyAll() встроенного монитора, но более гибкий: один ReentrantLock может иметь несколько независимых условий ожидания.
¶Основные методы
lock()— блокирующий захват блокировки. Если блокировка занята другим потоком, текущий поток переходит в состояние ожидания до её освобождения.tryLock()— неблокирующая попытка захвата. Возвращаетtrueпри успехе,false— если блокировка занята. Существует перегруженная версия с таймаутом и единицей времени.unlock()— освобождение блокировки. Должен вызываться в блокеfinallyдля гарантии освобождения даже при возникновении исключения.lockInterruptibly()— захват блокировки с возможностью прерывания ожидания потоком черезinterrupt().isLocked()— проверка, удерживается ли блокировка каким-либо потоком.getHoldCount()— возвращает число повторных захватов блокировки текущим потоком.isFair()— проверка режима справедливости.
¶Пример использования
```java import java.util.concurrent.locks.ReentrantLock;
public class Counter { private final ReentrantLock lock = new ReentrantLock(); private int count = 0;
public void increment() { lock.lock(); try { count++; } finally { lock.unlock(); } } } ```
В данном примере блокировка захватывается перед изменением общего ресурса и гарантированно освобождается в блоке finally.
¶Сравнение с synchronized
| Критерий | synchronized | ReentrantLock |
|---|---|---|
| Гибкость | Жёсткая структура блока | Ручное управление |
| Прерывание ожидания | Не поддерживается | Поддерживается |
| Таймаут захвата | Не поддерживается | Поддерживается |
| Несколько условий | Нет | Да (через Condition) |
| Справедливость | Не гарантируется | Настраивается |
| Производительность | Выше в простых случаях | Сопоставима, зависит от сценария |
¶Применение
ReentrantLock применяется в ситуациях, где требуется более тонкий контроль над синхронизацией, чем предоставляет synchronized. Типичные сценарии использования:
- реализация сложных алгоритмов с несколькими условиями ожидания;
- необходимость обхода блокировки с таймаутом для предотвращения взаимоблокировок;
- построение структур данных с высокой конкурентностью;
- реализация пулов ресурсов и очередей с приоритетами.
¶Критика и ограничения
Основным недостатком ReentrantLock считается необходимость ручного освобождения блокировки, что повышает риск ошибок программиста: забытый вызов unlock() приводит к взаимоблокировке. Кроме того, код с явными блокировками сложнее читать и поддерживать по сравнению с декларативным synchronized. В Java 8 и новее при использовании ReentrantLock рекомендуется применять идиому «try-with-resources» через вспомогательные классы или аккуратное использование try-finally.
¶Источники
- Документация Oracle Java SE: класс
java.util.concurrent.locks.ReentrantLock. - Ли Дуглас. «Concurrency in Practice» (Java Concurrency in Practice), 2006.
- Официальная спецификация Java API, пакет
java.util.concurrent.locks.