Ответ
Оба механизма обеспечивают взаимное исключение (mutual exclusion) и повторный вход (reentrancy), но ReentrantLock предоставляет более расширенные возможности.
synchronized — встроенное в язык ключевое слово. Синхронизация осуществляется либо на методе (весь метод), либо на объекте в блоке кода.
ReentrantLock — класс из пакета java.util.concurrent.locks, реализующий интерфейс Lock. Он требует явной блокировки и разблокировки.
| Сравнительная таблица: | Критерий | synchronized |
ReentrantLock |
|---|---|---|---|
| Получение блокировки | Неявное, при входе в блок/метод. | Явное, через вызов lock(). |
|
| Освобождение блокировки | Неявное, при выходе из блока/метода (даже при исключении). | Явное, через unlock() (обязательно в finally!). |
|
| Попытка блокировки с таймаутом | Нет. | Есть: tryLock(long timeout, TimeUnit unit). |
|
| Прерываемое ожидание | Нет. Поток, ожидающий блокировку, нельзя прервать. | Есть: lockInterruptibly(). |
|
| Честность (Fairness) | Не гарантируется. | Можно создать с политикой честности (new ReentrantLock(true)), что уменьшает голодание, но снижает пропускную способность. |
|
| Связка условий (Condition) | Одно встроенное условие ожидания (wait(), notify()). |
Можно создать несколько объектов Condition на один lock для более точного управления потоками. |
|
| Гибкость | Базовая, но лаконичная. | Высокая, но требует аккуратного управления. |
Пример synchronized:
public class Counter {
private int count = 0;
public synchronized void increment() {
count++; // Критическая секция защищена
}
// Или блочная синхронизация:
public void decrement() {
synchronized (this) {
count--;
}
}
}
Пример ReentrantLock с таймаутом и условием:
import java.util.concurrent.locks.*;
public class Buffer {
private final Lock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition(); // Условие "не полон"
private final Condition notEmpty = lock.newCondition(); // Условие "не пуст"
public void put(Object item) throws InterruptedException {
lock.lock();
try {
while (bufferIsFull()) {
notFull.await(); // Ждем, пока не освободится место
}
// ... добавить элемент ...
notEmpty.signal(); // Сигнализируем, что буфер не пуст
} finally {
lock.unlock(); // Освобождение в finally ОБЯЗАТЕЛЬНО
}
}
public Object take(long timeout, TimeUnit unit) throws InterruptedException {
lock.lock();
try {
if (!lock.tryLock(timeout, unit)) { // Попытка получить lock с таймаутом
throw new TimeoutException("Не удалось получить блокировку");
}
// ... взять элемент ...
return item;
} finally {
lock.unlock();
}
}
}
Рекомендация: Используйте synchronized для простых случаев, где его возможностей достаточно. Применяйте ReentrantLock, когда необходимы продвинутые функции: таймауты, прерываемые блокировки, несколько условий или честность.