Как написать код, который гарантированно приведет к взаимной блокировке (deadlock) в 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()).

Ответ 18+ 🔞

Да ты посмотри на эту красоту, классический дедлок, ёпта! Это ж как в жизни: два мудака схватились за свои ресурсы и оба ждут, пока другой отпустит, а сами ни хуя не делают. Замкнутый круг, блядь, вечный!

Вот смотри, как это гарантированно устроить, прям как в лабораторных условиях, сука.

Код, который точно всех положит:

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. Первый поток хватает 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. А LockB-то уже занят! И тут наш герой замирает навеки.
                    System.out.println("Thread 1: Acquired both locks!"); // Этой строки мы, блядь, не увидим.
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (LockB) { // 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. Тянется к LockA, а тот уже в цепких лапах первого! И второй тоже встал колом.
                    System.out.println("Thread 2: Acquired both locks!"); // И этой — нихуя.
                }
            }
        });

        thread1.start();
        thread2.start();
        // Всё, приехали. Два потока смотрят друг на друга, как дураки, и ждут до скончания времён.
    }
}

А почему это железный дедлок, спросишь ты? Да элементарно, Ватсон!

  1. Порядок — всё, блядь! Один берёт A потом B, второй — B потом A. Полный разброд и шатание, хуй с горы! Нет единого правила, вот они и перепутались.
  2. Задержка — мать подлости. Эти sleep(50) — это гениально, ёпта! Они дают каждому успеть схватить свой первый замок до того, как они полезут за чужим. Без них могло бы и пронести, а так — гарантия, пиздец.
  3. Цикл ебаный. Получается порочный круг: первому нужен B, который у второго, а второму нужен A, который у первого. Вротберунчик! И выхода нет, терпения ебать ноль.

А потом сидишь, смотришь — программа висит. Чтобы найти этих засранцев, юзай jstack — он тебе покажет, кто на ком повис, или из кода через ThreadMXBean.findDeadlockedThreads() — тоже отличная штука, сразу всех видно, кто кого заблокировал.