Почему завис?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Почему завис? (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 захватывает
lock1 - Поток 2 захватывает
lock2 - Поток 1 пытается захватить
lock2— заблокирован (ждёт Поток 2) - Поток 2 пытается захватить
lock1— заблокирован (ждёт Поток 1) - 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 — это серьёзная проблема, но её можно полностью избежать, если:
- Захватывать блокировки в одном порядке
- Использовать современные инструменты (ReentrantLock, ConcurrentCollections)
- Тестировать многопоточный код под нагрузкой
- Мониторить production с помощью thread dumps