Что такое взаимная блокировка (Deadlock) в программировании?

«Что такое взаимная блокировка (Deadlock) в программировании?» — вопрос из категории Java Core, который задают на 37% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

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

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

  1. Взаимное исключение (Mutual Exclusion): Ресурс не может использоваться несколькими потоками одновременно.
  2. Удержание и ожидание (Hold and Wait): Поток удерживает один ресурс и ожидает другой.
  3. Отсутствие вытеснения (No Preemption): Ресурс нельзя отнять у потока, только добровольно освободить.
  4. Циклическое ожидание (Circular Wait): Возникает цикл потоков, где каждый ожидает ресурс, удерживаемый следующим.

Пример на Java:

public class DeadlockExample {
    private static final Object lock1 = new Object();
    private static final Object lock2 = new Object();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            synchronized (lock1) {
                System.out.println("Thread 1: Holding lock1...");
                try { Thread.sleep(100); } catch (InterruptedException e) {}
                System.out.println("Thread 1: Waiting for lock2...");
                synchronized (lock2) { // Блокировка: lock2 удерживается t2
                    System.out.println("Thread 1: Acquired lock2!");
                }
            }
        });

        Thread t2 = new Thread(() -> {
            synchronized (lock2) {
                System.out.println("Thread 2: Holding lock2...");
                try { Thread.sleep(100); } catch (InterruptedException e) {}
                System.out.println("Thread 2: Waiting for lock1...");
                synchronized (lock1) { // Блокировка: lock1 удерживается t1
                    System.out.println("Thread 2: Acquired lock1!");
                }
            }
        });

        t1.start();
        t2.start(); // Потоки зависнут в deadlock.
    }
}

Стратегии предотвращения:

  • Упорядоченный захват ресурсов: Всегда захватывайте блокировки в строго одинаковом глобальном порядке (например, сначала lock1, потом lock2).
  • Использование tryLock с таймаутом: Вместо synchronized используйте ReentrantLock.tryLock(long timeout, TimeUnit unit) из пакета java.util.concurrent.locks.
  • Сокращение области видимости блокировки: Удерживайте блокировки минимально необходимое время.
  • Обнаружение и восстановление: Используйте мониторинг для выявления deadlock'ов (например, через ThreadMXBean.findDeadlockedThreads()) и принудительного прерывания одного из потоков.