Кейс (Java): Рассмотрите следующий пример, приводящий к взаимной блокировке (deadlock). Объясните причину и предложите корректные способы устранения —
synchronized(lockA) { synchronized(lockB) { /*...*/ } }
synchronized(lockB) { synchronized(lockA) { /*...*/ } }

13 Ноя в 09:40
14 +14
0
Ответы
1
Причина deadlock’а
- Оба потока берут разные мониторы в разном порядке: первый — сначала `lockA`, потом `lockB`; второй — сначала `lockB`, потом `lockA`. В результате возникает циклическое ожидание: T1 удерживает `lockA` и ждёт `lockB`, T2 удерживает `lockB` и ждёт `lockA` — классическая взаимная блокировка.
Корректные способы устранения (кратко, с примерами)
Способ — единый порядок захвата блокировок
- Правило: во всех участках кода всегда захватывать связанные блокировки в одном и том же порядке (например: сначала `lockA`, затем `lockB`).
Пример:
```java
// и в Thread1, и в Thread2 — один и тот же порядок
synchronized(lockA) {
synchronized(lockB) {
// работа с ресурсами A и B
}
}
```
Эффект: устраняет циклическое ожидание.
Способ — использование Lock.tryLock с таймаутом и откатом (ReentrantLock)
- Попытка захватить вторую блокировку с таймаутом; при неудаче отпустить первую, подождать и повторить — избегает бесконечной блокировки.
Пример:
```java
ReentrantLock lockA = new ReentrantLock();
ReentrantLock lockB = new ReentrantLock();
boolean doWork(long timeout, TimeUnit unit) throws InterruptedException {
if (!lockA.tryLock(timeout, unit)) return false;
try {
if (!lockB.tryLock(timeout, unit)) {
return false; // освободим lockA в finally и попробуем позже
}
try {
// работа с ресурсами
return true;
} finally {
lockB.unlock();
}
} finally {
lockA.unlock();
}
}
```
(например, `timeout = 100010001000` мс)
Способ — свести к одной блокировке / уменьшить область синхронизации
- Если возможно, объедините два lock в один (общий монитор) или уменьшите объём кода под synchronized, чтобы сократить время удержания блокировки.
Способ — использовать неблокирующие структуры из java.util.concurrent
- ConcurrentHashMap, Atomic-переменные, LongAdder и т.п. часто позволяют вообще отказаться от явных вложенных блокировок.
Способ — защита через confinement / immutable
- Делайте данные неизменяемыми или ограничьте доступ потоку (thread-local), чтобы синхронизация не требовалась.
Дополнительно — обнаружение и восстановление
- Для поиска проблем можно использовать ThreadMXBean (detectDeadlock), но это лишь диагностика; лучше устранить причину через дизайн.
Краткое резюме
- Самый простой и надёжный подход — обеспечить единую (детерминированную) последовательность захвата блокировок. При невозможности — использовать tryLock с таймаутом и откатом, или перейти на конструкции из java.util.concurrent / изменить дизайн, чтобы избежать вложенных блокировок.
13 Ноя в 09:48
Не можешь разобраться в этой теме?
Обратись за помощью к экспертам
Гарантированные бесплатные доработки в течение 1 года
Быстрое выполнение от 2 часов
Проверка работы на плагиат
Поможем написать учебную работу
Прямой эфир