Как написать код, который гарантированно приведет к взаимной блокировке (deadlock) в Java?

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

Ответ

Классический deadlock возникает, когда два или более потока блокируют разные ресурсы и каждый ждет освобождения ресурса, захваченного другим. Гарантированный deadlock можно создать, нарушив принцип упорядочивания блокировок и добавив задержку для синхронизации состояния потоков.

Пример кода, моделирующего deadlock:

public class GuaranteedDeadlock {
    private static final Object LockA = new Object();
    private static final Object LockB = new Object();

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            synchronized (LockA) { // 1. Поток 1 захватывает LockA
                System.out.println("Thread 1: Holding Lock A...");
                try {
                    Thread.sleep(50); // Задержка для гарантии, что оба потока захватят свои первые блокировки
                } catch (InterruptedException e) {}
                System.out.println("Thread 1: Waiting for Lock B...");
                synchronized (LockB) { // 3. Поток 1 пытается захватить LockB (но он у Thread 2) -> БЛОКИРОВКА
                    System.out.println("Thread 1: Acquired both locks!"); // Эта строка никогда не выполнится
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (LockB) { // 2. Поток 2 захватывает LockB
                System.out.println("Thread 2: Holding Lock B...");
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {}
                System.out.println("Thread 2: Waiting for Lock A...");
                synchronized (LockA) { // 4. Поток 2 пытается захватить LockA (но он у Thread 1) -> БЛОКИРОВКА
                    System.out.println("Thread 2: Acquired both locks!"); // Эта строка никогда не выполнится
                }
            }
        });

        thread1.start();
        thread2.start();
        // Оба потока теперь заблокированы навсегда.
    }
}

Почему deadlock гарантирован:

  1. Разный порядок блокировок: Thread 1 берет LockA, затем LockB. Thread 2 берет LockB, затем LockA.
  2. Синхронизация через sleep(): Задержка дает время каждому потоку захватить свой первый монитор до того, как они попытаются захватить второй.
  3. Циклическое ожидание: Каждый поток удерживает ресурс, нужный другому, и ждет, создавая неразрешимую циклическую зависимость.

Для обнаружения deadlock во время выполнения можно использовать jstack или JMX (ThreadMXBean.findDeadlockedThreads()).