Условия Коффмана — дедлок возможен, только если выполнены все четыре:
- взаимное исключение — ресурс может держать только один;
- удержание и ожидание — поток держит один ресурс и ждёт другой;
- нет принудительного отнятия — ресурс нельзя забрать, только отпустить добровольно;
- циклическое ожидание — есть цикл в графе «кто кого ждёт».
Профилактика — сломать хотя бы одно:
- единый порядок блокировок (ломает цикл) — например, всегда сначала счёт с меньшим id. Классический перевод денег
transfer(from, to) блокирует оба счёта; если один поток переводит A→B, а другой B→A, при захвате «сначала from» получится дедлок. Захват по порядку id устраняет его; - tryLock с таймаутом (ломает «нет отнятия» — поток сам отказывается): не получилось за время — отпустить всё, подождать случайное время и повторить;
- захват всех ресурсов сразу или одна общая блокировка (ломает удержание с ожиданием) — проще, но меньше параллелизма;
- не вызывать чужой код под блокировкой — колбэки и слушатели могут захватить что-то своё.
В базах данных дедлоки — обычное дело: две транзакции обновляют строки в разном порядке. СУБД строит граф ожиданий, находит цикл и откатывает одну транзакцию с ошибкой (deadlock detected в PostgreSQL). Приложение должно уметь повторить транзакцию; профилактика та же — обновлять строки в одном порядке.
Livelock — потоки не заблокированы, но не продвигаются: оба вежливо отпускают ресурс, видя конфликт, и снова сталкиваются. Лечится случайной задержкой перед повтором. Голодание — поток в принципе может продвинуться, но ему постоянно не достаётся ресурс (несправедливая блокировка, низкий приоритет, писатель при потоке читателей).
Диагностика: в Java jstack <pid> или дамп потоков сам пишет «Found one Java-level deadlock» с цепочкой; ThreadMXBean.findDeadlockedThreads. Симптом — процесс жив, CPU около нуля, запросы висят.