← Назад к вопросам

Почему завис?

2.2 Middle🔥 191 комментариев
#Docker, Kubernetes и DevOps#REST API и микросервисы

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Почему завис? (Deadlock в Java)

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

Классический пример: два потока, две блокировки

public class DeadlockExample {
    private final Object lock1 = new Object();
    private final Object lock2 = new Object();
    
    public void method1() {
        synchronized(lock1) {
            System.out.println("Thread " + Thread.currentThread().getName() + " got lock1");
            
            try { Thread.sleep(100); }  // Даём другому потоку время захватить lock2
            catch(InterruptedException e) {}
            
            synchronized(lock2) {
                System.out.println("Thread " + Thread.currentThread().getName() + " got lock2");
            }
        }
    }
    
    public void method2() {
        synchronized(lock2) {
            System.out.println("Thread " + Thread.currentThread().getName() + " got lock2");
            
            try { Thread.sleep(100); }
            catch(InterruptedException e) {}
            
            synchronized(lock1) {
                System.out.println("Thread " + Thread.currentThread().getName() + " got lock1");
            }
        }
    }
    
    public static void main(String[] args) {
        DeadlockExample example = new DeadlockExample();
        
        Thread t1 = new Thread(() -> {
            while(true) example.method1();
        });
        
        Thread t2 = new Thread(() -> {
            while(true) example.method2();
        });
        
        t1.start();
        t2.start();
        // Программа повесится!
    }
}

Что происходит?

  1. Поток 1 захватывает lock1
  2. Поток 2 захватывает lock2
  3. Поток 1 пытается захватить lock2 — заблокирован (ждёт Поток 2)
  4. Поток 2 пытается захватить lock1 — заблокирован (ждёт Поток 1)
  5. DEADLOCK — они ждут друг друга бесконечно

Четыре условия для deadlock

Для возникновения deadlock нужны все четыре условия одновременно:

1. Взаимное исключение (Mutual Exclusion)

  • Ресурс может использовать только один поток
  • Например: synchronized блок, мьютекс

2. Удержание (Hold and Wait)

  • Поток держит ресурс и ждёт другой
  • Он не освобождает ресурс, пока получит новый

3. Неперемещаемость (No Preemption)

  • Ресурс нельзя отобрать у потока
  • Поток должен явно его освободить

4. Циклическое ожидание (Circular Wait)

  • Поток A ждёт ресурса у B, B ждёт ресурса у A
  • Образуется цикл: A → B → A

Как избежать deadlock?

Вариант 1: Упорядочить захват блокировок

public class NoDeadlock {
    private final Object lock1 = new Object();
    private final Object lock2 = new Object();
    
    public void method1() {
        synchronized(lock1) {
            synchronized(lock2) {
                // Работа
            }
        }
    }
    
    public void method2() {
        synchronized(lock1) {  // Тот же порядок!
            synchronized(lock2) {
                // Работа
            }
        }
    }
}

Все методы захватывают блокировки в одном порядке → нет цикла.

Вариант 2: ReentrantReadWriteLock

private ReadWriteLock lock = new ReentrantReadWriteLock();

public void read() {
    lock.readLock().lock();
    try {
        // Несколько читателей одновременно
    } finally {
        lock.readLock().unlock();
    }
}

public void write() {
    lock.writeLock().lock();
    try {
        // Только один писатель
    } finally {
        lock.writeLock().unlock();
    }
}

Вариант 3: Временной лимит (timeout)

ReentrantLock lock1 = new ReentrantLock();
ReentrantLock lock2 = new ReentrantLock();

public void method1() {
    boolean acquired1 = false, acquired2 = false;
    try {
        acquired1 = lock1.tryLock(1, TimeUnit.SECONDS);
        if (!acquired1) return;  // Не получил — уходим
        
        acquired2 = lock2.tryLock(1, TimeUnit.SECONDS);
        if (!acquired2) return;  // Не получил — уходим
        
        // Работаем
    } catch(InterruptedException e) {
        Thread.currentThread().interrupt();
    } finally {
        if (acquired2) lock2.unlock();
        if (acquired1) lock1.unlock();
    }
}

Вариант 4: Использовать высокоуровневые конструкции

// ReentrantLock безопаснее, чем synchronized
ReentrantLock lock = new ReentrantLock();

public void work() {
    lock.lock();
    try {
        // Работа
    } finally {
        lock.unlock();  // Гарантированное освобождение
    }
}

// ConcurrentHashMap вместо synchronized HashMap
Map<String, String> map = new ConcurrentHashMap<>();

// BlockingQueue вместо notify/wait
BlockingQueue<Integer> queue = new LinkedBlockingQueue<>();
queue.put(42);  // Потокобезопасно
int value = queue.take();  // Потокобезопасно

Как отловить deadlock?

1. Используй JConsole или VisualVM

jconsole  # или visualvm

Эти инструменты показывают поток-диаграмму блокировок.

2. Получи thread dump

jstack <pid>  # Выведет все потоки и их стеки

3. Автоматическое обнаружение в коде

ThreadMXBean bean = ManagementFactory.getThreadMXBean();
long[] deadlockedThreadIds = bean.findDeadlockedThreads();
if (deadlockedThreadIds != null) {
    System.err.println("DEADLOCK DETECTED!");
}

Best Practices для многопоточности

  • Избегай synchronized — используй ReentrantLock, ConcurrentHashMap, BlockingQueue
  • Минимизируй критические секции — держи блокировку как можно меньше
  • Упорядочивай захват блокировок — одинаковый порядок везде
  • Используй timeouts — вместо бесконечного ожидания
  • Предпочитай immutable объекты — их не нужно синхронизировать
  • Используй high-level конструкцииjava.util.concurrent пакет

Вывод

Deadlock — это серьёзная проблема, но её можно полностью избежать, если:

  1. Захватывать блокировки в одном порядке
  2. Использовать современные инструменты (ReentrantLock, ConcurrentCollections)
  3. Тестировать многопоточный код под нагрузкой
  4. Мониторить production с помощью thread dumps