При каких условиях возникает взаимная блокировка (deadlock) в Java?

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

Ответ

Deadlock (взаимная блокировка) возникает при одновременном выполнении четырех условий (Условия Коффмана):

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

Классический пример на Java:

final Object lock1 = new Object();
final Object lock2 = new Object();

Thread t1 = new Thread(() -> {
    synchronized (lock1) {          // t1 захватывает lock1
        Thread.sleep(100);
        synchronized (lock2) { } // t1 ждет lock2 (удерживаемый t2)
    }
});

Thread t2 = new Thread(() -> {
    synchronized (lock2) {          // t2 захватывает lock2
        Thread.sleep(100);
        synchronized (lock1) { } // t2 ждет lock1 (удерживаемый t1) -> DEADLOCK
    }
});

t1.start();
t2.start();

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

  • Упорядочивание блокировок: Всегда захватывать locks в одном глобальном порядке (например, сначала lock1, потом lock2).
  • Использование tryLock с таймаутом из java.util.concurrent.locks.ReentrantLock.
  • Избегание вложенных синхронизированных блоков.
  • Использование высокоуровневых примитивов (ConcurrentHashMap, ExecutorService).