Причина 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 / изменить дизайн, чтобы избежать вложенных блокировок.
- Оба потока берут разные мониторы в разном порядке: первый — сначала `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 / изменить дизайн, чтобы избежать вложенных блокировок.