Короткий ответ
Оба механизма обеспечивают взаимное исключение и реентерабельны: поток, уже владеющий монитором или замком, может захватить его повторно. synchronized — встроенная конструкция языка: монитор захватывается при входе в метод или блок и автоматически освобождается при выходе, даже при исключении. ReentrantLock из java.util.concurrent.locks требует явного lock() и освобождения в finally, но даёт больше контроля.
Как это работает подробнее
Возможности synchronized ограничены: нельзя прервать поток, ожидающий блокировку, нельзя установить таймаут ожидания, нет честной (fair) очереди, а захват возможен только в пределах одного метода или блока.
ReentrantLock предоставляет tryLock() (захват без блокировки или с таймаутом), lockInterruptibly() (прерываемое ожидание), честные замки и несколько объектов Condition для тонкого управления ожиданием.
- Именно
tryLockс таймаутом позволяет писать код, устойчивый к deadlock: не получилось захватить блокировку — поток освобождает уже захваченные ресурсы и повторяет попытку.
Пример кода
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class ReentrantLockDemo {
private final Lock lock = new ReentrantLock();
void doWork() {
lock.lock();
try {
// критическая секция
} finally {
lock.unlock(); // обязательно в finally
}
}
}Что использовать на практике
- По умолчанию используй
synchronized: он проще, короче и исключает ошибку «забыл освободить». - Бери
ReentrantLock, когда нужны таймауты ожидания, прерывание ожидающего потока, честная очередность или несколькоCondition.