Что такое ReentrantLock в Java и каковы его основные особенности?

Ответ

ReentrantLock — это реализация интерфейса Lock из пакета java.util.concurrent.locks, предоставляющая механизм взаимного исключения (mutex) с расширенными возможностями по сравнению с ключевым словом synchronized.

Ключевые особенности:

  • Повторная входимость (Reentrancy): Поток, уже удерживающий блокировку, может захватить её повторно без взаимной блокировки (deadlock).
  • Честность (Fairness): При создании можно указать политику честности (fair). Честная блокировка (new ReentrantLock(true)) предоставляет доступ потокам в порядке FIFO, но снижает общую пропускную способность.
  • Прерываемое ожидание: Метод lockInterruptibly() позволяет прервать поток, ожидающий блокировку.
  • Попытка захвата с таймаутом: Метод tryLock(long timeout, TimeUnit unit) пытается получить блокировку в течение заданного времени.

Пример использования:

import java.util.concurrent.locks.ReentrantLock;

ReentrantLock lock = new ReentrantLock();

lock.lock(); // Захват блокировки
try {
    // Критическая секция
    sharedResource.modify();
} finally {
    lock.unlock(); // Освобождение блокировки ВСЕГДА в finally-блоке
}
Сравнение с synchronized: Особенность synchronized ReentrantLock
Поддержка tryLock Нет Да (с таймаутом)
Честность Нет (не гарантируется) Да (опционально)
Прерываемость Нет (блокировка не прерывается) Да (lockInterruptibly)
Условные переменные Одно (wait/notify) Множество (Condition)
Синтаксис Встроенное ключевое слово Явный API, требует unlock в finally

Когда использовать? ReentrantLock выбирают, когда необходимы продвинутые функции: неблокирующие попытки захвата (tryLock), честное планирование, работа с несколькими условиями ожидания (Condition) или необходимость прерываемого ожидания блокировки.

Ответ 18+ 🔞

Да ты посмотри, что за зверь такой — ReentrantLock! Это ж не просто какая-нибудь синхронизация, это целый арсенал из пакета java.util.concurrent.locks. Если synchronized — это как тупой кухонный нож, то ReentrantLock — это швейцарский армейский нож с фишками, от которых глаза на лоб лезут.

Чем он так охуенен, спросишь?

  • Повторная входимость (Reentrancy): Ну, представим, поток уже взял блокировку. А ему надо зайти ещё раз в метод, который тоже её требует. С обычным мьютексом — пиши пропало, deadlock, зависни и сдохни. А этот красавец говорит: «Да хуй с тобой, заходи, ты же свой, блядь». Захватил — и всё, никакой взаимной блокировки.
  • Честность (Fairness): Тут вообще цирк. Можно создать блокировку с флагом честности (new ReentrantLock(true)). Тогда она, эта сволочь, будет пускать потоки строго по очереди, как в советской поликлинике — кто первый встал, того и тапки. Правда, производительность просядет, как у мудака после трёх дней запоя, но зато справедливость, мать её!
  • Прерываемое ожидание: Метод lockInterruptibly() — это просто песня. Ждёт поток блокировку, а ты ему — раз! — interrupt(). И он не будет висеть, как идиот, до скончания веков, а вежливо так, с пониманием, вылетит с InterruptedException. Красота!
  • Попытка захвата с таймаутом: tryLock(long timeout, TimeUnit unit) — это вообще верх наглости. «Дай-ка, — говорит поток, — попробую взять блокировку, но если за 5 секунд не выйдет — да похуй, пойду другие дела делать». Не получилось — и не надо, без истерик.

Как этим пользоваться, чтобы не обосраться? Смотри, вот пример, его в голове выгравируй:

import java.util.concurrent.locks.ReentrantLock;

ReentrantLock lock = new ReentrantLock();

lock.lock(); // Всё, взял замок, теперь я царь и бог
try {
    // Твоя критическая секция. Делай что хочешь с общим ресурсом.
    sharedResource.modifyTheFuckOutOfIt();
} finally {
    lock.unlock(); // ЭТО ВАЖНО, БЛЯДЬ! Освобождай ВСЕГДА в finally!
}

Запомни, как «Отче наш»: unlock() — только в finally. Иначе если в критической секции вылетит исключение — твоя блокировка навечно повиснет, и все остальные потоки будут ждать её, как дураки, до тепловой смерти вселенной.

Ну и что лучше: synchronized или этот ваш ReentrantLock? Сравнительная таблица, ёпта, чтобы всё стало ясно:

Особенность synchronized (старый дедок) ReentrantLock (продвинутый чувак)
Поддержка tryLock Нет, жди пока не синешь Да, и с таймаутом, чтоб не засиживался
Честность Хуй там, а не честность. Кто как схватит. Да, можно включить, если хочешь справедливости (но медленнее)
Прерываемость Нет. Заблокировался — терпи, мудак. Да, можно вежливо попросить прекратить (lockInterruptibly)
Условные переменные Одна, через wait()/notify(). Тесно, душно. Множество, через Condition. Раздельные очереди ожидания — просто космос!
Синтаксис Встроено в язык, просто и коротко. Явный API. Больше власти, но и больше ответственности. Не забудь unlock()!

Так когда же его, блядь, использовать? Да когда тебе мало возможностей этого убогого synchronized! Когда нужно:

  • Сказать «попробуй взять блокировку, а не получится — и хуй с ним» (tryLock).
  • Устроить честную очередь из потоков (честный fair режим).
  • Работать с несколькими условиями ожидания (например, «очередь полна» и «очередь пуста» — это два разных Condition).
  • Иметь возможность вежливо прервать поток, который ждёт блокировку.

В общем, ReentrantLock — это для тех, кто перерос примитивные игрушки и хочет контролировать многопоточность, как бог. Главное — не накосячить с unlock(), а то будет овердохуища проблем.