Home » Взаимная блокировка (Deadlock) в Java: пример и объяснение
Взаимная блокировка (Deadlock) в Java: пример и объяснение

Взаимная блокировка (Deadlock) в Java: пример и объяснение

Deadlock — это классическая проблема многопоточного программирования, которая может превратить ваш сервер в кирпич. Если вы деплоите Java-приложения на продакшн, то рано или поздно столкнётесь с этой проблемой. Особенно болезненно это проявляется в высоконагруженных системах, где несколько потоков пытаются захватить одни и те же ресурсы. Понимание механизмов взаимной блокировки поможет избежать простоев сервера, правильно настроить мониторинг и быстро диагностировать проблемы в production.

Как работает Deadlock — теория без лишней воды

Deadlock возникает, когда два или более потока взаимно блокируют друг друга, ожидая освобождения ресурсов. Классический пример: поток A держит ресурс 1 и ждёт ресурс 2, а поток B держит ресурс 2 и ждёт ресурс 1. Получается замкнутый круг.

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

  • Взаимное исключение — ресурс может использоваться только одним потоком
  • Удержание и ожидание — поток держит ресурс и ждёт другой
  • Невозможность прерывания — ресурс нельзя отобрать принудительно
  • Циклическое ожидание — существует цикл из потоков, ждущих друг друга

Практический пример — воспроизводим deadlock

Давайте создадим простой пример, который гарантированно приведёт к deadlock:

public class DeadlockExample {
    private static final Object lock1 = new Object();
    private static final Object lock2 = new Object();
    
    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            synchronized (lock1) {
                System.out.println("Thread 1: Locked lock1");
                try {
                    Thread.sleep(100); // Имитируем работу
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
                System.out.println("Thread 1: Waiting for lock2");
                synchronized (lock2) {
                    System.out.println("Thread 1: Locked lock2");
                }
            }
        });
        
        Thread thread2 = new Thread(() -> {
            synchronized (lock2) {
                System.out.println("Thread 2: Locked lock2");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
                System.out.println("Thread 2: Waiting for lock1");
                synchronized (lock1) {
                    System.out.println("Thread 2: Locked lock1");
                }
            }
        });
        
        thread1.start();
        thread2.start();
    }
}

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

Как диагностировать deadlock в production

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

# Найти PID Java-процесса
ps aux | grep java

# Получить дамп потоков
jstack  > thread_dump.txt

# Или использовать kill для получения дампа
kill -3 

# Для анализа deadlock в реальном времени
jconsole # GUI-утилита для мониторинга JVM

# Или через командную строку
jstat -gc  1s # Мониторинг GC каждую секунду

В thread dump вы увидите что-то вроде:

Found 2 deadlocks.

Java stack information for the threads listed above:
===================================================
"Thread-0":
        at DeadlockExample.lambda$main$0(DeadlockExample.java:15)
        - waiting to lock <0x000000076ab62208> (a java.lang.Object)
        - locked <0x000000076ab62218> (a java.lang.Object)
        at DeadlockExample$$Lambda$1/1078694789.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)

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

Вот проверенные методы борьбы с взаимными блокировками:

1. Упорядочивание блокировок

// Плохо - может привести к deadlock
synchronized (lock1) {
    synchronized (lock2) {
        // работа
    }
}

// Хорошо - всегда блокируем в одном порядке
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();

public void method1() {
    synchronized (lock1) {
        synchronized (lock2) {
            // работа
        }
    }
}

public void method2() {
    synchronized (lock1) { // Тот же порядок!
        synchronized (lock2) {
            // работа
        }
    }
}

2. Использование tryLock с timeout

import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.TimeUnit;

public class SafeLockExample {
    private static final ReentrantLock lock1 = new ReentrantLock();
    private static final ReentrantLock lock2 = new ReentrantLock();
    
    public void doWork() {
        boolean acquired1 = false;
        boolean acquired2 = false;
        
        try {
            acquired1 = lock1.tryLock(5, TimeUnit.SECONDS);
            if (!acquired1) {
                System.out.println("Не удалось получить lock1");
                return;
            }
            
            acquired2 = lock2.tryLock(5, TimeUnit.SECONDS);
            if (!acquired2) {
                System.out.println("Не удалось получить lock2");
                return;
            }
            
            // Выполняем работу
            System.out.println("Работа выполнена");
            
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            if (acquired2) lock2.unlock();
            if (acquired1) lock1.unlock();
        }
    }
}

Сравнение подходов к синхронизации

Подход Плюсы Минусы Когда использовать
synchronized Простота, встроенная поддержка Нет timeout, нельзя прервать Простые случаи
ReentrantLock Гибкость, timeout, interruption Сложнее в использовании Сложная логика синхронизации
Concurrent Collections Thread-safe из коробки Ограниченная функциональность Стандартные коллекции
Atomic операции Высокая производительность Только для примитивов Счётчики, флаги

Мониторинг и автоматизация

Для автоматического детектирования deadlock в production используйте:

// Программное обнаружение deadlock
ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
long[] deadlockedThreads = threadBean.findDeadlockedThreads();

if (deadlockedThreads != null) {
    ThreadInfo[] threadInfos = threadBean.getThreadInfo(deadlockedThreads);
    for (ThreadInfo threadInfo : threadInfos) {
        System.out.println("Deadlock detected: " + threadInfo.getThreadName());
    }
}

Скрипт для мониторинга на сервере:

#!/bin/bash
# deadlock_monitor.sh
JAVA_PID=$(pgrep -f "java.*myapp")
if [ -n "$JAVA_PID" ]; then
    jstack $JAVA_PID | grep -A 5 "deadlock"
    if [ $? -eq 0 ]; then
        echo "DEADLOCK DETECTED!" | mail -s "Deadlock Alert" admin@example.com
    fi
fi

Альтернативные решения и инструменты

Для решения проблем с многопоточностью существуют альтернативы:

  • Actor Model (Akka) — избегает shared state
  • Reactive Streams — асинхронная обработка без блокировок
  • Lock-free структуры данных — используют Compare-And-Swap
  • Software Transactional Memory — транзакционный подход

Полезные ссылки:

Интересные факты и нестандартные применения

Знали ли вы, что deadlock может возникнуть не только с блокировками? Вот необычные случаи:

  • Database deadlock — две транзакции блокируют строки в разном порядке
  • Network deadlock — буферы TCP заполнены, и процессы ждут друг друга
  • Философы за столом — классическая задача, демонстрирующая deadlock
// Пример с базой данных
// Транзакция 1:
BEGIN;
UPDATE users SET name='John' WHERE id=1;  -- Блокирует запись 1
UPDATE users SET name='Jane' WHERE id=2;  -- Ждёт запись 2

// Транзакция 2:
BEGIN;
UPDATE users SET name='Bob' WHERE id=2;   -- Блокирует запись 2
UPDATE users SET name='Alice' WHERE id=1; -- Ждёт запись 1

Настройка JVM для борьбы с deadlock

Полезные JVM флаги для диагностики:

# Автоматический дамп при deadlock
-XX:+PrintConcurrentLocks
-XX:+PrintGCDetails
-XX:+HeapDumpOnOutOfMemoryError

# Более детальная информация о блокировках
-XX:+TraceBiasedLocking
-XX:+PrintLockStatistics

# Настройка для high-load серверов
-XX:+UseBiasedLocking
-XX:BiasedLockingStartupDelay=0

Если вы разворачиваете Java-приложения на собственном сервере, рекомендую рассмотреть VPS с достаточным объёмом RAM для комфортной работы с JVM или выделенный сервер для высоконагруженных приложений.

Заключение и рекомендации

Deadlock — это серьёзная проблема, которая может полностью парализовать ваше приложение. Основные правила для предотвращения:

  • Всегда блокируйте ресурсы в одном порядке
  • Используйте timeout для блокировок
  • Минимизируйте время удержания блокировок
  • Предпочитайте concurrent коллекции обычным с synchronized
  • Настройте мониторинг и алерты

В production обязательно используйте инструменты для мониторинга JVM — jstack, jconsole, и современные APM решения. Правильная диагностика поможет быстро найти проблему и восстановить работу сервиса.

Помните: лучший deadlock — это deadlock, который не возник. Проектируйте архитектуру с минимальным количеством shared state, используйте immutable объекты где возможно, и всегда тестируйте многопоточный код под нагрузкой.


В этой статье собрана информация и материалы из различных интернет-источников. Мы признаем и ценим работу всех оригинальных авторов, издателей и веб-сайтов. Несмотря на то, что были приложены все усилия для надлежащего указания исходного материала, любая непреднамеренная оплошность или упущение не являются нарушением авторских прав. Все упомянутые товарные знаки, логотипы и изображения являются собственностью соответствующих владельцев. Если вы считаете, что какой-либо контент, использованный в этой статье, нарушает ваши авторские права, немедленно свяжитесь с нами для рассмотрения и принятия оперативных мер.

Данная статья предназначена исключительно для ознакомительных и образовательных целей и не ущемляет права правообладателей. Если какой-либо материал, защищенный авторским правом, был использован без должного упоминания или с нарушением законов об авторском праве, это непреднамеренно, и мы исправим это незамедлительно после уведомления. Обратите внимание, что переиздание, распространение или воспроизведение части или всего содержимого в любой форме запрещено без письменного разрешения автора и владельца веб-сайта. Для получения разрешений или дополнительных запросов, пожалуйста, свяжитесь с нами.

Leave a reply

Your email address will not be published. Required fields are marked