ReentrantLock и synchronized
Сравните возможности ReentrantLock (tryLock, lockInterruptibly, справедливость) с ключевым словом synchronized.
«ReentrantLock и synchronized» — бесплатный урок Java Academy на CoddyKit. Это урок 1 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Java Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Java Academy содержит 4 уроков всего.
Ключевое слово синхронизации
Ключевое слово synchronized в Java обеспечивает взаимное исключение с помощью внутренней блокировки объекта (монитора). Это простой, но ограниченный механизм — без ограничения времени ожидания, справедливого доступа и возможности прерывания.
class Counter {
private int count = 0;
synchronized void increment() {
count++; // only one thread at a time
}
synchronized int get() { return count; }
}
Counter c = new Counter();
// Multiple threads can safely call increment()Основы ReentrantLock
ReentrantLock — это явная блокировка из java.util.concurrent.locks. Она обеспечивает такое же взаимное исключение, как и synchronized, но предоставляет больше возможностей управления. Блокировку необходимо вручную освобождать в блоке finally.
import java.util.concurrent.locks.*;
class SafeCounter {
private int count = 0;
private final ReentrantLock lock = new ReentrantLock();
void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock(); // always release in finally!
}
}
int get() {
lock.lock();
try { return count; }
finally { lock.unlock(); }
}
}tryLock: неблокирующий захват
tryLock() пытается захватить блокировку, не блокируя поток. Возвращает положительный результат при успехе и отрицательный — если блокировка занята другим потоком.
ReentrantLock lock = new ReentrantLock();
if (lock.tryLock()) {
try {
System.out.println("Got the lock, doing work");
} finally {
lock.unlock();
}
} else {
System.out.println("Lock busy, skipping or retrying");
}tryLock с ограничением времени
Ожидайте заданное время, чтобы захватить блокировку:
try {
if (lock.tryLock(500, TimeUnit.MILLISECONDS)) {
try {
// work with protected resource
System.out.println("Acquired within 500ms");
} finally {
lock.unlock();
}
} else {
System.out.println("Timed out waiting for lock");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}lockInterruptibly
lockInterruptibly() захватывает блокировку, если поток не был прерван. Это полезно для отменяемых задач, ожидающих блокировку:
try {
lock.lockInterruptibly(); // throws InterruptedException if interrupted
try {
// do work
} finally {
lock.unlock();
}
} catch (InterruptedException e) {
System.out.println("Interrupted while waiting for lock");
Thread.currentThread().interrupt();
}Повторный захват
И synchronized, и ReentrantLock допускают повторный захват: поток, который уже владеет блокировкой, может захватить её снова без взаимной блокировки:
synchronized void outer() {
System.out.println("outer");
inner(); // same thread re-enters — OK
}
synchronized void inner() {
System.out.println("inner"); // same lock, same thread
}
// ReentrantLock:
ReentrantLock lock = new ReentrantLock();
lock.lock();
lock.lock(); // acquire again — hold count = 2
lock.unlock(); // hold count = 1
lock.unlock(); // hold count = 0, lock releasedПолитика справедливого доступа
Создайте справедливую ReentrantLock, чтобы предоставлять доступ в порядке FIFO. Это предотвращает голодание потоков, но снижает пропускную способность:
// Unfair (default): no ordering guarantee, better throughput
ReentrantLock unfair = new ReentrantLock();
// Fair: threads acquire in arrival order
ReentrantLock fair = new ReentrantLock(true);
System.out.println(fair.isFair()); // trueУсловные переменные
ReentrantLock предоставляет объекты условий — они гибче, чем wait()/notify():
ReentrantLock lock2 = new ReentrantLock();
Condition notEmpty = lock2.newCondition();
Queue<String> queue = new LinkedList<>();
// Producer
lock2.lock();
try {
queue.offer("item");
notEmpty.signal(); // wake one waiting thread
} finally { lock2.unlock(); }
// Consumer
lock2.lock();
try {
while (queue.isEmpty()) notEmpty.await(); // wait & release lock
System.out.println(queue.poll());
} finally { lock2.unlock(); }Когда использовать ReentrantLock, а когда synchronized
Используйте synchronized, если:
- требуется простое взаимное исключение;
- не нужны ограничение времени ожидания, справедливый доступ и возможность прерывания.
Используйте ReentrantLock, если:
- нужен tryLock() или tryLock() с ограничением времени;
- нужен lockInterruptibly();
- требуется справедливый порядок доступа;
- нужно несколько условных переменных.
Счётчик блокировки и мониторинг
ReentrantLock предоставляет диагностические методы:
ReentrantLock lock3 = new ReentrantLock();
lock3.lock();
lock3.lock(); // reentrant — hold count 2
System.out.println(lock3.getHoldCount()); // 2
System.out.println(lock3.isHeldByCurrentThread()); // true
System.out.println(lock3.isLocked()); // true
System.out.println(lock3.getQueueLength()); // 0 (no waiting threads)
lock3.unlock();
lock3.unlock(); // fully releasedРаспространённая ошибка: освобождение незахваченной блокировки
Вызов unlock(), когда текущий поток не владеет блокировкой, вызывает IllegalMonitorStateException. Всегда объединяйте захват и освобождение блокировки в пару или используйте конструкцию try-finally:
ReentrantLock lock4 = new ReentrantLock();
try {
// If lock() is never called (e.g., skipped by exception before this line)
lock4.unlock(); // throws IllegalMonitorStateException
} catch (IllegalMonitorStateException e) {
System.out.println("Must lock before unlock!");
}Быстрая проверка
Какой метод ReentrantLock позволяет потоку захватить блокировку или сразу вернуть результат без блокировки, если блокировка недоступна?
Итоги: ReentrantLock и synchronized
Главное:
- Оба механизма обеспечивают взаимное исключение и повторный захват
- ReentrantLock добавляет tryLock, lockInterruptibly, справедливый доступ и условные переменные
- Всегда освобождайте блокировку в блоке finally
- synchronized проще — предпочитайте его, когда его возможностей достаточно
- Используйте ReentrantLock в сложных сценариях: при ограничении времени ожидания, возможности прерывания и нескольких условных переменных
Часто задаваемые вопросы
Урок «ReentrantLock и synchronized» бесплатный?
Да — полный текст урока «ReentrantLock и synchronized» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Java Academy, подпишись на CoddyKit PRO. Курс Java Academy содержит 4 уроков всего.
Чему я научусь в уроке «ReentrantLock и synchronized»?
Сравните возможности ReentrantLock (tryLock, lockInterruptibly, справедливость) с ключевым словом synchronized. Ты практикуешь Java Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Java Academy?
Предыдущий опыт не требуется. Java Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 1 из 4.
Сколько времени занимает урок «ReentrantLock и synchronized»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Java Academy?
Да. Каждый урок Java Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- ReentrantLock и synchronized
- ReadWriteLock для сценариев чтения и записи
- Атомарные переменные: обновления без блокировок
- StampedLock и оптимистичное чтение