Как получить DeadLock между двумя потоками?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
# 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):
- Mutual Exclusion — ресурс может быть занят только одним потоком
- Hold and Wait — потоки держат ресурсы и ждут других
- No Preemption — нет способа отнять ресурс у потока
- 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:
- Упорядочивай блокировки — захватывай в одном порядке
- Используй timeout — не ждите бесконечно
- Минимизируй синхронизацию — синхронизируй только необходимое
- Используй современные структуры — ConcurrentHashMap, ReentrantLock с timeout
- Обнаруживай — используй jstack или ThreadMXBean для отладки
Понимание deadlock критично для написания безопасного многопоточного кода.