Что такое deadlock?
Взаимная блокировка (deadlock) — это ситуация, при которой два или более потоков навечно заблокированы, ожидая друг от друга освобождения ресурсов, которые они удерживают.
Аналогия из жизни: два автомобиля встретились на узком мосту, каждый ждёт, пока другой уступит дорогу. Ни один не может двигаться — оба стоят навсегда.
Условия возникновения (условия Коффмана)
Deadlock возникает при одновременном выполнении всех четырёх условий:
| Условие | Описание |
|---|---|
| Mutual exclusion (взаимное исключение) | Хотя бы один ресурс используется в режиме эксклюзивного доступа |
| Hold and wait (удержание и ожидание) | Поток удерживает ресурс и запрашивает дополнительный, удерживаемый другим потоком |
| No preemption (отсутствие принудительного освобождения) | Ресурс не может быть принудительно отобран у потока — только добровольное освобождение |
| Circular wait (циклическое ожидание) | Существует замкнутая цепочка потоков, каждый из которых ждёт ресурс, удерживаемый следующим |
Пример deadlock
Код, приводящий к deadlock
final Object lockA = new Object();
final Object lockB = new Object();
// Поток 1: захватывает A, потом B
new Thread(() -> {
synchronized (lockA) {
try { Thread.sleep(100); } catch (InterruptedException e) { }
synchronized (lockB) {
System.out.println("Поток 1: захватил A и B");
}
}
}).start();
// Поток 2: захватывает B, потом A — обратный порядок!
new Thread(() -> {
synchronized (lockB) {
try { Thread.sleep(100); } catch (InterruptedException e) { }
synchronized (lockA) {
System.out.println("Поток 2: захватил B и A");
}
}
}).start();
// Результат: deadlock — оба потока ждут друг друга бесконечно
Способы предотвращения
Достаточно нарушить любое из четырёх условий Коффмана:
- Упорядочивание блокировок: всегда захватывать блокировки в одном и том же порядке и освобождать в обратном.
- Использование
tryLock(timeout):ReentrantLock.tryLock()позволяет задать таймаут и отступить при неудаче, вместо бесконечного ожидания. - Минимизация критических секций: чем меньше кода под блокировкой, тем меньше шанс deadlock.
- Lock-free алгоритмы: использование
Atomic*-переменных и concurrent-коллекций вместо явных блокировок.
Способы диагностики
jstack <PID>— показывает дамп потоков с обнаруженными deadlock.jcmd <PID> Thread.print— аналогично jstack.- JMX:
ThreadMXBean.findDeadlockedThreads()— программная проверка. - IDE: IntelliJ IDEA, VisualVM — визуальная диагностика потоков.
Программное обнаружение deadlock
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();
if (deadlockedThreads != null) {
ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(deadlockedThreads, true, true);
for (ThreadInfo info : threadInfos) {
System.out.println("Deadlocked thread: " + info.getThreadName());
System.out.println(" Waiting for: " + info.getLockName());
System.out.println(" Held by: " + info.getLockOwnerName());
}
}
Вывод
Deadlock — одна из самых опасных проблем многопоточности, потому что программа зависает без какого-либо исключения или сообщения об ошибке. Ключевая стратегия предотвращения — строгий порядок захвата блокировок и использование tryLock с таймаутом.
На собеседовании: перечислите четыре условия Коффмана, приведите пример кода с deadlock и способ его исправления (упорядочивание блокировок). Бонус — покажите, как программно обнаружить deadlock через
ThreadMXBean.