Что такое deadlock?
Deadlock (взаимная блокировка) — это ситуация в многопоточном программировании, при которой два или более потока навсегда блокированы, ожидая друг друга, и ни один из них не может продолжить выполнение.
🔁 Пример на интуитивном уровне
Представьте двух человек:
-
У одного — вилка, у другого — нож.
-
Чтобы поесть, каждому нужны и вилка, и нож.
-
Каждый держит свой предмет и ждёт, когда другой отпустит второй.
-
Никто не отпускает — все стоят и ждут вечно.
Это и есть deadlock.
🔧 Пример на Kotlin/Java
val lock1 = Any()
val lock2 = Any()
fun thread1() {
synchronized(lock1) {
Thread.sleep(100)
synchronized(lock2) {
println("Thread 1 acquired both locks")
}
}
}
fun thread2() {
synchronized(lock2) {
Thread.sleep(100)
synchronized(lock1) {
println("Thread 2 acquired both locks")
}
}
}
Если thread1() и thread2() запускаются одновременно:
-
Поток 1 захватывает lock1, ждёт lock2.
-
Поток 2 захватывает lock2, ждёт lock1.
→ Оба навсегда заблокированы.
📌 Условия возникновения deadlock (все 4 обязательны)
-
Взаимное исключение (Mutual exclusion)
— Ресурсы могут использоваться только одним потоком в момент времени. -
Удержание и ожидание (Hold and wait)
— Поток удерживает один ресурс и ждёт другой. -
Невозможность принудительного освобождения (No preemption)
— Захваченный ресурс нельзя отобрать у потока. -
Циклическое ожидание (Circular wait)
— Потоки образуют цикл, где каждый ждёт ресурс, занятый другим.
🔍 Как обнаружить deadlock
-
Потоки «зависают» и не завершаются.
-
Использование средств мониторинга (jconsole, VisualVM, jstack).
-
В логах — потоки в состоянии BLOCKED или WAITING на одни и те же ресурсы.
🛡 Как избежать deadlock
**Всегда блокировать ресурсы в одном порядке **
// Всегда сначала lock1, потом lock2
synchronized(lock1) {
synchronized(lock2) {
// ...
}
}
**1. Использовать tryLock() с таймаутом (ReentrantLock)
python**
if (lock1.tryLock(100, TimeUnit.MILLISECONDS)) {
try {
if (lock2.tryLock(100, TimeUnit.MILLISECONDS)) {
try {
// critical section
} finally {
lock2.unlock()
}
}
} finally {
lock1.unlock()
}
}
2. Избегать вложенных блокировок, где возможно.
3. Применять тайм-ауты и прерываемые блокировки.
4. Выделить отдельный поток для управления ресурсами (например, пул исполнителей, очереди).
🧠 Deadlock vs Livelock vs Starvation
Состояние | Описание |
---|---|
Deadlock | Потоки навсегда заблокированы, ждут друг друга. |
--- | --- |
Livelock | Потоки активно "уступают", но не делают полезной работы. |
--- | --- |
Starvation | Один поток не получает ресурсы из-за приоритета других. |
--- | --- |
Итого
Deadlock — это логическая ошибка в многопоточном коде, которая возникает при неправильной организации захвата ресурсов. Он приводит к полной остановке работы связанных потоков. Чтобы его избежать, нужно контролировать порядок блокировок, избегать циклов ожидания, использовать современные синхронизационные инструменты и анализировать архитектуру многопоточности.