Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Deadlock: опыт и практика
Да, я сталкивался с deadlock ситуациями в production код и это был ценный опыт для понимания многопоточности в Java.
Классический пример Deadlock
Дeadlock происходит когда два потока ждут друг друга в циклической зависимости:
public class BankAccount {
private long balance;
public BankAccount(long balance) {
this.balance = balance;
}
public synchronized void transfer(BankAccount target, long amount) {
if (this.balance >= amount) {
this.balance -= amount;
target.balance += amount;
}
}
}
// Deadlock ситуация:
BankAccount account1 = new BankAccount(1000);
BankAccount account2 = new BankAccount(2000);
Thread t1 = new Thread(() -> {
account1.transfer(account2, 100); // Ждёт lock на account1, потом на account2
});
Thread t2 = new Thread(() -> {
account2.transfer(account1, 200); // Ждёт lock на account2, потом на account1
});
t1.start();
t2.start();
// Deadlock! t1 имеет lock на account1 и ждет account2
// t2 имеет lock на account2 и ждет account1
Четыре условия для Deadlock
Для возникновения deadlock необходимо выполнение ВСЕ четырёх условий:
- Mutual Exclusion — ресурс может держать только один поток
- Hold and Wait — поток держит ресурс и ждет другого
- No Preemption — нет принудительного отбора ресурса
- Circular Wait — циклическая цепочка потоков, ждущих ресурсы
Как я решал эту проблему
1. Упорядочение блокировок (Lock Ordering):
public class BankAccount {
private long id;
private long balance;
private final Object lock;
public void transfer(BankAccount target, long amount) {
// Всегда блокируем в одном порядке: сначала меньший ID
BankAccount first = this.id < target.id ? this : target;
BankAccount second = this.id < target.id ? target : this;
synchronized (first) {
synchronized (second) {
if (this.balance >= amount) {
this.balance -= amount;
target.balance += amount;
}
}
}
}
}
2. Использование ReadWriteLock:
ReadWriteLock lock = new ReentrantReadWriteLock();
public void transfer(long amount) {
lock.writeLock().lock();
try {
// critical section
} finally {
lock.writeLock().unlock();
}
}
3. Использование ConcurrentHashMap и atomic операций:
ConcurrentHashMap<String, AtomicLong> balances = new ConcurrentHashMap<>();
public void transfer(String from, String to, long amount) {
// Атомарные операции избегают deadlock
balances.computeIfPresent(from, (k, v) -> new AtomicLong(v.get() - amount));
balances.computeIfPresent(to, (k, v) -> new AtomicLong(v.get() + amount));
}
Обнаружение Deadlock
// ThreadMXBean помогает детектировать deadlock
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();
if (deadlockedThreads != null && deadlockedThreads.length > 0) {
System.out.println("Deadlock detected!");
ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(deadlockedThreads);
for (ThreadInfo threadInfo : threadInfos) {
System.out.println(threadInfo);
}
}
Ключевые выводы
- Избегайте nested locks — используйте один lock когда возможно
- Используйте timeout при блокировке
- Применяйте концентрический lock ordering для нескольких ресурсов
- Профилируйте многопоточный код и используйте инструменты вроде JVisualVM
- Тестируйте под высокой нагрузкой