Получить доступ

Что такое взаимная блокировка и как её избежать?

MiddleКонкурентность
Ещё спрашивают так:
  • Что такое deadlock?
  • Какие условия нужны для дедлока?
  • Чем livelock отличается от deadlock?
  • Как найти дедлок в работающем приложении?
Коротко

Взаимная блокировка — потоки ждут друг друга по кругу: первый держит A и ждёт B, второй держит B и ждёт A. Никто не продвинется.

  • Нужны все 4 условия Коффмана: взаимное исключение, удержание с ожиданием, нет отнятия, цикл ожидания
  • Главная профилактика — единый порядок захвата блокировок
  • Ещё: tryLock с таймаутом, одна крупная блокировка, отказ от вложенных блокировок

А ещё Livelock — потоки активно уступают друг другу и тоже не продвигаются; голодание — поток бесконечно ждёт своей очереди

Так подсказку видит пользователь sotto на живом собеседовании — за пару секунд, пока вы думаете вслух.

Попробуйте сами

Взаимная блокировка двух потоков

Подробно

Условия Коффмана — дедлок возможен, только если выполнены все четыре:

  1. взаимное исключение — ресурс может держать только один;
  2. удержание и ожидание — поток держит один ресурс и ждёт другой;
  3. нет принудительного отнятия — ресурс нельзя забрать, только отпустить добровольно;
  4. циклическое ожидание — есть цикл в графе «кто кого ждёт».

Профилактика — сломать хотя бы одно:

  • единый порядок блокировок (ломает цикл) — например, всегда сначала счёт с меньшим 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 около нуля, запросы висят.

Пример

void transfer(Account from, Account to, long amount) {
    Account first = from.id < to.id ? from : to;    // единый порядок
    Account second = first == from ? to : from;
    synchronized (first) {
        synchronized (second) {
            from.withdraw(amount);
            to.deposit(amount);
        }
    }
}

Что хотят услышать

  • Определение через циклическое ожидание и пример с двумя блокировками.
  • Четыре условия Коффмана.
  • Способы предотвращения: порядок блокировок, tryLock с таймаутом.
  • Дедлоки в базе данных и повтор транзакции.
  • Отличие от livelock и голодания, диагностика — плюс.

Частые ошибки

  • Путать дедлок с гонкой.
  • Предлагать «увеличить таймауты» как решение.

Связанные вопросы

Закрытая бета

Подсказки на собеседовании — в реальном времени

sotto слышит вопрос интервьюера и показывает короткий ответ и план, как в блоке «Коротко». Оставьте контакт — пришлём доступ и промокод.

  • Промокод на 2 часа — сверх пробных 30 минут
  • Программа для Windows — скачать сразу, войти — через Яндекс ID или VK ID
  • Прямая связь с разработчиком: что улучшить — сделаем