Ответ
Зависание программы часто вызвано взаимной блокировкой (deadlock), когда два или более потока бесконечно ожидают ресурсы, захваченные друг другом. Для возникновения deadlock необходимо одновременное выполнение четырех условий (условия Коффмана):
- Взаимное исключение: ресурс не может быть использован более чем одним потоком одновременно.
- Удержание и ожидание: поток удерживает один ресурс и ожидает доступ к другому.
- Отсутствие вытеснения: ресурс может быть освобожден только добровольно удерживающим его потоком.
- Циклическое ожидание: возникает кольцевая цепочка потоков, каждый из которых ждет ресурс, удерживаемый следующим в цепочке.
Типичный пример deadlock на Java:
final Object lock1 = new Object();
final Object lock2 = new Object();
new Thread(() -> {
synchronized (lock1) {
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lock2) { // Поток будет ждать, пока lock2 не освободится
System.out.println("Thread 1 acquired both locks");
}
}
}).start();
new Thread(() -> {
synchronized (lock2) {
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lock1) { // Поток будет ждать, пока lock1 не освободится
System.out.println("Thread 2 acquired both locks");
}
}
}).start();
// Оба потока зависнут.
Стратегии предотвращения и решения:
- Упорядочивание блокировок: всегда захватывать несколько блокировок в одном и том же глобальном порядке (например, сначала
lock1, потомlock2). - Использование
tryLockизjava.util.concurrent.locks.ReentrantLockс таймаутом. - Анализ дампов потоков с помощью
jstack <PID>или профилировщиков (VisualVM, JProfiler) для выявления циклов ожидания. - Проектирование без избыточной синхронизации: использование потокобезопасных коллекций из
java.util.concurrentи атомарных операций.