Приведён фрагмент Java-кода с синхронизацией: synchronized void transfer(Account a, Account b, int sum) { synchronized(a){ synchronized(b){ a.balance -= sum; b.balance += sum; } } }; объясните, как здесь может возникнуть дедлок и предложите изменения дизайна для его предотвращения

14 Ноя в 10:42
1 +1
0
Ответы
1
Почему дедлок может возникнуть
- Два потока делают transfer(A,B) и transfer(B,A). Первый захватывает монитор A, второй — монитор B; затем каждый ждёт второй монитор — классический взаимный блок (deadlock).
Краткая диаграмма:
- поток1: locked A → пытается locked B
- поток2: locked B → пытается locked A
Способы предотвращения (с примерами)
1) Жёсткий глобальный порядок захвата блокировок
- Всегда захватывать аккаунты в одном и том же порядке (по id или по System.identityHashCode).
- Например, если использовать id: сначала блокируем аккаунт с меньшим id.
Пример:
```java
synchronized void transfer(Account a, Account b, int sum) {
if (a.getId() == b.getId()) { // перевод самому себе
return;
}
Object first = (a.getId() < b.getId()) ? a : b;
Object second = (a.getId() < b.getId()) ? b : a;
synchronized(first) {
synchronized(second) {
a.balance -= sum;
b.balance += sum;
}
}
}
```
(здесь сравнение вида a.getId()<b.getId()a.getId() < b.getId()a.getId()<b.getId())
2) Использовать java.util.concurrent.locks с tryLock и откатом
- Пытаемся захватить обе блокировки с таймаутом; при неудаче — освобождаем и повторяем (backoff). Это предотвращает вечную блокировку.
Схема:
```java
boolean lockedA = lockA.tryLock(1000, TimeUnit.MILLISECONDS);
if (!lockedA) { /* повторить или сообщить об ошибке */ }
try {
boolean lockedB = lockB.tryLock(1000, TimeUnit.MILLISECONDS);
if (!lockedB) { /* отпустить lockA и повторить */ }
try {
// операция
} finally { lockB.unlock(); }
} finally { if (lockedA) lockA.unlock(); }
```
(таймауты, например, 100010001000 мс)
3) Один глобальный монитор / менеджер переводов
- Синхронизировать на объекте Bank или вести очередь переводов. Простое и надёжное, но снижает параллелизм.
4) Альтернативы высокого уровня
- Использовать транзакционные подходы (STM) или атомарные структуры данных.
- Хранить баланс в AtomicLong и выполнять корректную логику с CAS — подходит не для всех сценариев.
Рекомендация
- Простейший и надёжный паттерн: фиксированный порядок захвата (вариант 1). Если важен высокий параллелизм и много конфликтов — использовать tryLock с таймаутом (вариант 2) или дизайн очереди переводов.
14 Ноя в 11:35
Не можешь разобраться в этой теме?
Обратись за помощью к экспертам
Гарантированные бесплатные доработки в течение 1 года
Быстрое выполнение от 2 часов
Проверка работы на плагиат
Поможем написать учебную работу
Прямой эфир