← Назад к вопросам

Сталкивался ли с Deadlock

2.8 Senior🔥 201 комментариев
#Многопоточность

Комментарии (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 необходимо выполнение ВСЕ четырёх условий:

  1. Mutual Exclusion — ресурс может держать только один поток
  2. Hold and Wait — поток держит ресурс и ждет другого
  3. No Preemption — нет принудительного отбора ресурса
  4. 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
  • Тестируйте под высокой нагрузкой