Как избежать дедлок в многопоточном коде
Что проверяют
Знаете ли вы условия, при которых возникает взаимная блокировка, и умеете ли разрушить хотя бы одно из них на практике, а не только произнести «используйте таймауты». Следом почти всегда просят рассказать, как найти дедлок в живом приложении.
Как отвечать
Начните с условий Коффмана: взаимное исключение, удержание с ожиданием, отсутствие вытеснения и циклическое ожидание. Дедлок возможен, только когда выполняются все четыре, поэтому каждая техника бьёт по одному из них:
- единый порядок захвата: все потоки берут замки в одном глобальном порядке, например по id счёта при переводе между двумя счетами. Цикл ожидания исчезает;
tryLockс таймаутом:lock.tryLock(100, TimeUnit.MILLISECONDS); не удалось взять второй замок, отпускаем первый и повторяем после случайной паузы;- меньше вложенных замков: короткие критические секции, никаких вызовов чужого кода (слушателей, колбэков) под замком;
- высокоуровневые средства:
ConcurrentHashMap,AtomicLong,BlockingQueueи неизменяемые объекты часто убирают явные блокировки вовсе.
Упомяните, что synchronized таймаута не умеет и прерыванием не снимается, а lockInterruptibly() у ReentrantLock позволяет прервать ожидание. Для диагностики: jstack <pid> или jcmd <pid> Thread.print печатают блок «Found one Java-level deadlock», программно то же даёт ThreadMXBean.findDeadlockedThreads().
Сильный ход: вспомнить дедлок без замков. Задача в пуле фиксированного размера, которая синхронно ждёт результат другой задачи из того же пула, зависает, когда все потоки пула заняты такими ожиданиями.
Где спотыкаются
Путают дедлок с livelock. Потоки, которые повторяют tryLock без случайной задержки, могут бесконечно уступать друг другу: никто не заблокирован, а работа стоит.
Пишут lock() внутри try, а unlock() в finally. Если второй замок не был взят, unlock() на нём бросит IllegalMonitorStateException и скроет исходную ошибку. Замок берут до try, на каждый свой блок.
Упорядочивают захват по System.identityHashCode() и забывают про совпадение хэшей. Для равных значений нужен третий, «разрешающий» замок, иначе порядок снова неоднозначен.