Gymterview
junior

Что такое deadlock?

Взаимная блокировка (deadlock) — это ситуация, при которой два или более потоков навечно заблокированы, ожидая друг от друга освобождения ресурсов, которые они удерживают.

Аналогия из жизни: два автомобиля встретились на узком мосту, каждый ждёт, пока другой уступит дорогу. Ни один не может двигаться — оба стоят навсегда.

Условия возникновения (условия Коффмана)

Deadlock возникает при одновременном выполнении всех четырёх условий:

Условие Описание
Mutual exclusion (взаимное исключение) Хотя бы один ресурс используется в режиме эксклюзивного доступа
Hold and wait (удержание и ожидание) Поток удерживает ресурс и запрашивает дополнительный, удерживаемый другим потоком
No preemption (отсутствие принудительного освобождения) Ресурс не может быть принудительно отобран у потока — только добровольное освобождение
Circular wait (циклическое ожидание) Существует замкнутая цепочка потоков, каждый из которых ждёт ресурс, удерживаемый следующим

Пример deadlock

Код, приводящий к deadlock
final Object lockA = new Object();
final Object lockB = new Object();

// Поток 1: захватывает A, потом B
new Thread(() -> {
    synchronized (lockA) {
        try { Thread.sleep(100); } catch (InterruptedException e) { }
        synchronized (lockB) {
            System.out.println("Поток 1: захватил A и B");
        }
    }
}).start();

// Поток 2: захватывает B, потом A — обратный порядок!
new Thread(() -> {
    synchronized (lockB) {
        try { Thread.sleep(100); } catch (InterruptedException e) { }
        synchronized (lockA) {
            System.out.println("Поток 2: захватил B и A");
        }
    }
}).start();
// Результат: deadlock — оба потока ждут друг друга бесконечно

Способы предотвращения

Достаточно нарушить любое из четырёх условий Коффмана:

  • Упорядочивание блокировок: всегда захватывать блокировки в одном и том же порядке и освобождать в обратном.
  • Использование tryLock(timeout): ReentrantLock.tryLock() позволяет задать таймаут и отступить при неудаче, вместо бесконечного ожидания.
  • Минимизация критических секций: чем меньше кода под блокировкой, тем меньше шанс deadlock.
  • Lock-free алгоритмы: использование Atomic*-переменных и concurrent-коллекций вместо явных блокировок.

Способы диагностики

  • jstack <PID> — показывает дамп потоков с обнаруженными deadlock.
  • jcmd <PID> Thread.print — аналогично jstack.
  • JMX: ThreadMXBean.findDeadlockedThreads() — программная проверка.
  • IDE: IntelliJ IDEA, VisualVM — визуальная диагностика потоков.
Программное обнаружение deadlock
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();

if (deadlockedThreads != null) {
    ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(deadlockedThreads, true, true);
    for (ThreadInfo info : threadInfos) {
        System.out.println("Deadlocked thread: " + info.getThreadName());
        System.out.println("  Waiting for: " + info.getLockName());
        System.out.println("  Held by: " + info.getLockOwnerName());
    }
}

Вывод

Deadlock — одна из самых опасных проблем многопоточности, потому что программа зависает без какого-либо исключения или сообщения об ошибке. Ключевая стратегия предотвращения — строгий порядок захвата блокировок и использование tryLock с таймаутом.

На собеседовании: перечислите четыре условия Коффмана, приведите пример кода с deadlock и способ его исправления (упорядочивание блокировок). Бонус — покажите, как программно обнаружить deadlock через ThreadMXBean.