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

Как получить DeadLock между двумя потоками?

1.7 Middle🔥 181 комментариев
#Многопоточность

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

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

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

# Deadlock между двумя потоками в Java

Что такое 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) {
        // Поток 1
        Thread thread1 = new Thread(() -> {
            synchronized(lock1) {
                System.out.println("Поток 1: захватил lock1");
                
                try {
                    Thread.sleep(100); // Даем время потоку 2 захватить lock2
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                
                System.out.println("Поток 1: ждет lock2...");
                synchronized(lock2) { // ← Deadlock! lock2 уже захвачен потоком 2
                    System.out.println("Поток 1: захватил lock2");
                }
            }
        }, "Thread-1");
        
        // Поток 2
        Thread thread2 = new Thread(() -> {
            synchronized(lock2) {
                System.out.println("Поток 2: захватил lock2");
                
                try {
                    Thread.sleep(100); // Даем время потоку 1 захватить lock1
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                
                System.out.println("Поток 2: ждет lock1...");
                synchronized(lock1) { // ← Deadlock! lock1 уже захвачен потоком 1
                    System.out.println("Поток 2: захватил lock1");
                }
            }
        }, "Thread-2");
        
        thread1.start();
        thread2.start();
        
        // Программа зависнет здесь (deadlock)
        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        System.out.println("Программа завершилась"); // Это не выполнится
    }
}

Результат выполнения:

Поток 1: захватил lock1
Поток 2: захватил lock2
Поток 1: ждет lock2...
Поток 2: ждет lock1...
(программа зависает)

Условия возникновения Deadlock (необходимо все 4):

  1. Mutual Exclusion — ресурс может быть занят только одним потоком
  2. Hold and Wait — потоки держат ресурсы и ждут других
  3. No Preemption — нет способа отнять ресурс у потока
  4. Circular Wait — циклические зависимости между потоками

Пример 2: Deadlock с методами класса

public class Account {
    private int balance;
    private int id;
    
    public Account(int id, int balance) {
        this.id = id;
        this.balance = balance;
    }
    
    public synchronized void withdraw(int amount) {
        System.out.println(Thread.currentThread().getName() + " выводит " + amount);
        balance -= amount;
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    
    public synchronized void deposit(int amount) {
        System.out.println(Thread.currentThread().getName() + " пополняет " + amount);
        balance += amount;
    }
    
    public int getId() {
        return id;
    }
}

public class DeadlockAccountExample {
    public static void main(String[] args) {
        Account account1 = new Account(1, 1000);
        Account account2 = new Account(2, 2000);
        
        // Поток 1: переводит с account1 на account2
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 3; i++) {
                account1.withdraw(100);
                account2.deposit(100);
            }
        }, "Поток-1");
        
        // Поток 2: переводит с account2 на account1
        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 3; i++) {
                account2.withdraw(100);
                account1.deposit(100);
            }
        }, "Поток-2");
        
        thread1.start();
        thread2.start();
    }
}

Пример 3: ReentrantLock Deadlock

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockDeadlock {
    private static ReentrantLock lock1 = new ReentrantLock();
    private static ReentrantLock lock2 = new ReentrantLock();
    
    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            lock1.lock();
            System.out.println("Поток 1: захватил lock1");
            
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            
            System.out.println("Поток 1: пытается захватить lock2");
            lock2.lock(); // Deadlock
            try {
                System.out.println("Поток 1: захватил lock2");
            } finally {
                lock2.unlock();
                lock1.unlock();
            }
        }, "Thread-1");
        
        Thread thread2 = new Thread(() -> {
            lock2.lock();
            System.out.println("Поток 2: захватил lock2");
            
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            
            System.out.println("Поток 2: пытается захватить lock1");
            lock1.lock(); // Deadlock
            try {
                System.out.println("Поток 2: захватил lock1");
            } finally {
                lock1.unlock();
                lock2.unlock();
            }
        }, "Thread-2");
        
        thread1.start();
        thread2.start();
    }
}

Как обнаружить Deadlock

1. Через jstack

Если программа зависла, можно использовать jstack для анализа потоков:

# Найти PID процесса
jps

# Получить dump потоков
jstack <PID>

# Ищем строки вида:
# "Поток-1" waiting to lock monitor
# "Поток-2" waiting to lock monitor

2. Программно — ThreadMXBean

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadMXBean;

public class DeadlockDetector {
    public static void main(String[] args) throws InterruptedException {
        // Запустить deadlock пример
        DeadlockExample.main(args);
        
        // Дать время для deadlock
        Thread.sleep(2000);
        
        // Обнаружить deadlock
        ThreadMXBean bean = ManagementFactory.getThreadMXBean();
        long[] threadIds = bean.findDeadlockedThreads();
        
        if (threadIds != null && threadIds.length > 0) {
            System.out.println("DEADLOCK ОБНАРУЖЕН!");
            System.out.println("Количество потоков в deadlock: " + threadIds.length);
            
            var threadInfo = bean.getThreadInfo(threadIds);
            for (var info : threadInfo) {
                System.out.println("\nПоток: " + info.getThreadName());
                System.out.println("Состояние: " + info.getThreadState());
                System.out.println("Ждет монитор: " + info.getLockName());
            }
        }
    }
}

Как избежать Deadlock

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

// ❌ Deadlock подвержено
public synchronized void transfer1(Account other) {
    this.withdraw(100);
    other.deposit(100);
}

// ✅ Правильно — всегда захватываем в одном порядке
public static synchronized void transfer(Account from, Account to, int amount) {
    // Захватываем в порядке возрастания ID
    Account first = from.getId() < to.getId() ? from : to;
    Account second = from.getId() < to.getId() ? to : from;
    
    synchronized(first) {
        synchronized(second) {
            from.withdraw(amount);
            to.deposit(amount);
        }
    }
}

2. Использовать timeout

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

public class DeadlockPreventionWithTimeout {
    private static ReentrantLock lock1 = new ReentrantLock();
    private static ReentrantLock lock2 = new ReentrantLock();
    
    public static void safeOperation() throws InterruptedException {
        boolean lock1Acquired = lock1.tryLock(1, TimeUnit.SECONDS);
        
        if (!lock1Acquired) {
            System.out.println("Не удалось захватить lock1");
            return;
        }
        
        try {
            boolean lock2Acquired = lock2.tryLock(1, TimeUnit.SECONDS);
            
            if (!lock2Acquired) {
                System.out.println("Не удалось захватить lock2");
                return;
            }
            
            try {
                // Выполняем операцию
                System.out.println("Операция выполнена");
            } finally {
                lock2.unlock();
            }
        } finally {
            lock1.unlock();
        }
    }
}

3. Использовать высокоуровневые структуры (ConcurrentHashMap и др.)

import java.util.concurrent.ConcurrentHashMap;

// ✅ ConcurrentHashMap защищена от deadlock
// Используется segment locking вместо глобальной синхронизации
public class SafeCounterExample {
    private ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
    
    public void increment(String key) {
        map.compute(key, (k, v) -> v == null ? 1 : v + 1);
    }
}

4. Использовать ReadWriteLock

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockExample {
    private ReadWriteLock lock = new ReentrantReadWriteLock();
    private int value = 0;
    
    public int read() {
        lock.readLock().lock();
        try {
            return value;
        } finally {
            lock.readLock().unlock();
        }
    }
    
    public void write(int newValue) {
        lock.writeLock().lock();
        try {
            value = newValue;
        } finally {
            lock.writeLock().unlock();
        }
    }
}

Таблица профилактики Deadlock

ПроблемаРешение
Циклические зависимостиУпорядочить захват блокировок
Неопределенное время ожиданияИспользовать timeout (tryLock)
Множественные синхронизированные методыИспользовать ReentrantLock
Конфликты доступаИспользовать ConcurrentHashMap, CopyOnWriteArrayList
Сложная логика синхронизацииИспользовать ReadWriteLock

Лучшие практики

Избегай вложенной синхронизации — синхронизируй только необходимые участки

Упорядочивай захват блокировок — если нужны две блокировки, захватывай в одном порядке

Используй timeout — tryLock с timeout вместо lock()

Используй высокоуровневые структуры — ConcurrentHashMap, CopyOnWriteArrayList

Минимизируй критическую секцию — чем меньше, тем меньше шанс deadlock

Не используй synchronized на публичных объектах — внешний код может заблокировать

Не вызывай другие методы внутри synchronized — они могут тоже синхронизировать

Заключение

Deadlock возникает, когда два потока циклически ждут друг друга. Это серьезная ошибка параллелизма. Чтобы избежать deadlock:

  1. Упорядочивай блокировки — захватывай в одном порядке
  2. Используй timeout — не ждите бесконечно
  3. Минимизируй синхронизацию — синхронизируй только необходимое
  4. Используй современные структуры — ConcurrentHashMap, ReentrantLock с timeout
  5. Обнаруживай — используй jstack или ThreadMXBean для отладки

Понимание deadlock критично для написания безопасного многопоточного кода.