ReentrantLock.unlock(): примеры (JAVA)
ReentrantLock.unlock(): voidОписание метода
Метод ReentrantLock.unlock() относится к классу java.util.concurrent.locks.ReentrantLock. Он используется для освобождения захваченного блокировкой потока. В отличие от синхронизированного блока (intrinsic lock), ReentrantLock предоставляет дополнительные возможности: справедливость, возможность прерывания при ожидании, попытки захвата с таймаутом и получение состояния захвата.
Аргументы: отсутствуют. Сигнатура: public void unlock().
Возвращаемое значение: нет (тип void). Поведение: при вызове уменьшает счетчик удержания блокировки (hold count) для вызывающего потока. Если счетчик становится равен нулю, блокировка полностью освобождается и может быть захвачена другим потоком.
Исключения и особые случаи:
- Если текущий поток не владеет блокировкой, бросается
IllegalMonitorStateException. Это самая частая ошибка при некорректном использовании unlock(). - Метод не блокирует; он немедленно возвращает управление либо бросает исключение.
- ReentrantLock поддерживает повторный захват: один и тот же поток может вызвать lock() несколько раз, тогда требуется соответствующее число вызовов unlock() для полного освобождения.
Сопутствующие методы класса полезны при отладке и управлении: isHeldByCurrentThread(), getHoldCount(), isLocked(), tryLock(), lockInterruptibly() и newCondition(). Они не изменяют поведение unlock(), но помогают избежать ошибок.
Короткие примеры использования
1) Базовый паттерн с try/finally
import java.util.concurrent.locks.ReentrantLock;
public class Example1 {
private static final ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
lock.lock();
try {
System.out.println("Внутри критической секции");
} finally {
lock.unlock();
}
}
}
Внутри критической секции
2) Неправильный вызов unlock() из потока, не владеющего блокировкой
import java.util.concurrent.locks.ReentrantLock;
public class Example2 {
private static final ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
lock.lock();
try { Thread.sleep(1000); } catch (InterruptedException ignored) {}
lock.unlock();
});
t1.start();
Thread.sleep(100); // пусть t1 захватит
// второй поток пытается отпустить без захвата
Thread t2 = new Thread(() -> {
try {
lock.unlock();
} catch (Exception e) {
System.out.println(e.getClass().getSimpleName() + ": " + e.getMessage());
}
});
t2.start();
t1.join(); t2.join();
}
}
IllegalMonitorStateException: null
3) Повторный захват и соответствующее количество освобождений
import java.util.concurrent.locks.ReentrantLock;
public class Example3 {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
lock.lock();
lock.lock(); // повторный захват
System.out.println("Hold count: " + lock.getHoldCount());
lock.unlock();
System.out.println("Hold count after one unlock: " + lock.getHoldCount());
lock.unlock();
System.out.println("Hold count after second unlock: " + lock.getHoldCount());
}
}
Hold count: 2 Hold count after one unlock: 1 Hold count after second unlock: 0
4) tryLock и возможный отказ в захвате
import java.util.concurrent.locks.ReentrantLock;
public class Example4 {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
lock.lock();
boolean got = lock.tryLock();
System.out.println("Попытка захвата без ожидания: " + got);
lock.unlock();
}
}
Попытка захвата без ожидания: false
Аналоги и варианты в Java
- synchronized (встроенный монитор): проще в использовании, автоматически освобождается при выходе из блока; не предоставляет гибких опций, таких как tryLock с таймаутом или справедливость.
- ReentrantReadWriteLock: полезен, когда требуется разделять чтение и запись; чтение допускает конкурентный доступ, запись - эксклюзивный.
- StampedLock: предлагает оптимистичные чтения и более тонкое управление блокировками, но не является полностью reentrant и требует осторожности.
- Semaphore и CountDownLatch: используются для управления количеством одновременно выполняемых операций или ожидания событий, но не для взаимного исключения в классическом виде reentrant-lock.
Выбор между ReentrantLock и встроенным монитором зависит от требований: если нужна гибкость (tryLock, таймаут, interruptible lock, справедливость), ReentrantLock предпочтительнее; если нужно простое блокирование, synchronized обычно проще и безопаснее.
Альтернативы в других языках
- Python:
threading.RLock()имеет методыacquire()иrelease(), поведение аналогично ReentrantLock. Пример:
import threading
r = threading.RLock()
r.acquire()
try:
print('Внутри RLock')
finally:
r.release()
Внутри RLock
- C#: встроенная конструкция
lock(obj)использует Monitor.Enter/Exit; явный Monitor.Exit(obj) эквивалентен unlock, но требует корректного владения. Пример:
using System;
using System.Threading;
class Program {
static object obj = new object();
static void Main() {
Monitor.Enter(obj);
try {
Console.WriteLine("В секции");
} finally {
Monitor.Exit(obj);
}
}
}
В секции
- Go:
sync.Mutexс методамиLock()иUnlock(). Unlock() вызывает панику при некорректном вызове со стороны не владеющего горутины. Пример:
package main
import (
"fmt"
"sync"
)
func main() {
var mu sync.Mutex
mu.Lock()
fmt.Println("Locked")
mu.Unlock()
fmt.Println("Unlocked")
}
Locked Unlocked
- JavaScript: в однопоточном окружении Node.js локи обычно не требуются; при работе с worker threads используются сторонние библиотеки (например async-mutex) с API
acquire()/release()или Promise-обертками. Пример концептуальный:
// npm i async-mutex
const { Mutex } = require('async-mutex');
const mutex = new Mutex();
mutex.acquire().then(release => {
console.log('В секции');
release();
});
В секции
- PHP: в стандартном исполнении потоков нет; при использовании расширения pthreads или системных семафоров используются соответствующие API (mutex/sem) с похожими концепциями but синтаксис и поведение отличаются.
- Kotlin: использует те же классы Java, например java.util.concurrent.locks.ReentrantLock, поведение совпадает.
- SQL: в СУБД используются advisory locks (например, pg_advisory_lock в PostgreSQL). Они работают на уровне БД и отличаются от блокировок в памяти приложения.
- Lua: нативных потоков нет, используются сторонние библиотеки (Lua Lanes и др.), где предоставляются свои механизмы синхронизации.
Типичные ошибки при использовании unlock()
- Отсутствие finally при освобождении блокировки. Результат: блокировка может не освободиться при исключении, что ведет к дедлоку. Пример:
ReentrantLock lock = new ReentrantLock();
lock.lock();
// Исключение до unlock
throw new RuntimeException("Ошибка");
// unlock() никогда не вызовется
(в результате возникнет зависший ресурс, методы ожидающие lock будут блокироваться)
- Вызов unlock() из потока, который не удерживает блокировку. Результат: IllegalMonitorStateException. Пример выше (см. Examples 2).
- Недостаточное количество вызовов unlock() после повторных lock(). Результат: блокировка остается удержанной, другие потоки блокируются. Пример: lock.lock(); lock.lock(); lock.unlock(); - требуется еще один unlock().
- Ожидание внутри критической секции без освобождения блокировки (например, долгие блокирующие операции) приводит к снижению параллелизма и возможным дедлокам.
- Попытка использовать Condition.signal()/await() без соблюдения правил владения lock: await освобождает lock и затем заново захватывает; signal требует владения lock при вызове signal(). Неправильный порядок вызывает IllegalMonitorStateException или логические ошибки.
Изменения и история
Класс ReentrantLock и метод unlock() введены в Java 5 (JDK 1.5) в рамках пакета java.util.concurrent. С тех пор сигнатура public void unlock() и базовое поведение не менялись. Добавлялись вспомогательные методы в разные релизы (например, getHoldCount(), isHeldByCurrentThread(), isLocked()), но сам алгоритм освобождения и возможные исключения остались прежними. Новых изменений в семантике unlock() в последних версиях не отмечено.
Расширенные и нестандартные примеры
1) Использование Condition: await() освобождает блокировку и затем восстанавливает владение при пробуждении. Важно вызывать unlock() в finally.
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionExample {
private final ReentrantLock lock = new ReentrantLock();
private final Condition cond = lock.newCondition();
private boolean ready = false;
public void awaiter() throws InterruptedException {
lock.lock();
try {
while (!ready) {
cond.await(); // временно освобождает lock
}
System.out.println("Условие выполнено");
} finally {
lock.unlock();
}
}
public void signaler() {
lock.lock();
try {
ready = true;
cond.signalAll();
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
ConditionExample ex = new ConditionExample();
Thread t = new Thread(() -> {
try { ex.awaiter(); } catch (InterruptedException ignored) {}
});
t.start();
Thread.sleep(100);
ex.signaler();
}
}
Условие выполнено
2) Обёртка для try-with-resources: создание AutoCloseable-адаптера для автоматического вызова unlock()
import java.util.concurrent.locks.ReentrantLock;
public class LockAutoCloseable implements AutoCloseable {
private final ReentrantLock lock;
public LockAutoCloseable(ReentrantLock lock) {
this.lock = lock;
this.lock.lock();
}
@Override
public void close() {
lock.unlock();
}
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
try (LockAutoCloseable lac = new LockAutoCloseable(lock)) {
System.out.println("Внутри try-with-resources");
}
}
}
Внутри try-with-resources
3) tryLock с таймаутом и реакция на отказ в захвате
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public class TryLockTimeout {
public static void main(String[] args) throws InterruptedException {
ReentrantLock lock = new ReentrantLock();
lock.lock();
Thread t = new Thread(() -> {
try {
if (lock.tryLock(500, TimeUnit.MILLISECONDS)) {
try { System.out.println("Захват выполнен"); } finally { lock.unlock(); }
} else {
System.out.println("Не удалось захватить за отведенное время");
}
} catch (InterruptedException e) {
System.out.println("Поток прерван");
}
});
t.start();
Thread.sleep(1000);
lock.unlock();
t.join();
}
}
Не удалось захватить за отведенное время
4) Справедливость (fair) против несправедливого режима: в справедливом режиме потоки получают блокировку в порядке очереди ожидания. Демонстрация может показать изменение порядка обслуживания, однако в небольших примерах результат может варьироваться из-за планировщика.
5) Рекурсивные вызовы: демонстрация использования lock/unlock через несколько уровней вызовов функции. Важно, чтобы количество unlock соответствовало количеству lock.
import java.util.concurrent.locks.ReentrantLock;
public class RecursiveExample {
private static final ReentrantLock lock = new ReentrantLock();
public static void recurse(int depth) {
lock.lock();
try {
System.out.println("Depth: " + depth);
if (depth > 0) recurse(depth - 1);
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
recurse(3);
}
}
Depth: 3 Depth: 2 Depth: 1 Depth: 0
6) Нестандартное: контроль состояния блокировки в отладочных логах без вмешательства в логику синхронизации (использовать isHeldByCurrentThread() и getHoldCount() только для диагностики). Пример пропускается по компактности, но рекомендуется вызывать в безопасном месте, чтобы избежать TOCTOU-ошибок.
джава ReentrantLock.unlock() function comments
- джава ReentrantLock.unlock() - аргументы и возвращаемое значение
- Функция java ReentrantLock.unlock() - описание
- ReentrantLock.unlock() - примеры
- ReentrantLock.unlock() - похожие методы на java
- ReentrantLock.unlock() на javascript, c#, python, php
- ReentrantLock.unlock() изменения java
- Примеры ReentrantLock.unlock() на джава