Deadlock
Deadlock — цикл ожидания: каждый участник удерживает ресурс, нужный следующему, и никто не может продолжить.
// Поток A
synchronized (left) {
synchronized (right) {
move();
}
}
// Поток B
synchronized (right) {
synchronized (left) {
move();
}
}Если A захватил left, а B — right, оба навсегда ждут второй монитор.
Условия возникновения
Классическая модель выделяет четыре одновременных условия:
- ресурс нельзя совместно использовать;
- поток удерживает один ресурс, ожидая другой;
- ресурс нельзя принудительно отобрать;
- существует цикл ожидания.
Устранение хотя бы одного условия разрушает deadlock.
Предотвращение
Единый порядок блокировок
Все операции захватывают сущности по стабильному ключу:
Account first = a.id() < b.id() ? a : b;
Account second = a.id() < b.id() ? b : a;
synchronized (first.lock()) {
synchronized (second.lock()) {
transfer(a, b, amount);
}
}Нужно отдельно обработать одинаковый ключ и перевод объекта самому себе.
Ограниченное ожидание
ReentrantLock.tryLock(timeout, unit) позволяет отказаться, освободить уже полученное и повторить с backoff. Это не доказывает отсутствие deadlock, но дает путь восстановления.
Уменьшение области lock
Не вызывать сеть, пользовательский callback или другой компонент под своей блокировкой. Если нужен согласованный снимок — скопировать состояние под lock, затем делать I/O вне него.
Диагностика
Thread dump показывает потоки, удерживаемые мониторы и waiting to lock. JVM часто прямо отмечает найденный Java-level deadlock. Нужно восстановить граф «поток → ожидаемый lock → владелец», а не ориентироваться только на BLOCKED.
На собеседовании
- Что обязательно для deadlock? Циклическое ожидание при удержании ресурсов.
- Самая практичная профилактика? Глобальный порядок захвата.
- Поможет ли interrupt потоку у входа в synchronized? Нет, ожидание intrinsic monitor не interruptible.
- Чем starvation отличается? Поток долго не получает ресурс, но система в целом движется.
- Как подтвердить deadlock? Thread dump и граф владельцев/ожиданий.