Как возникают deadlock в Java и какие параметры вызывают блокировку
Что проверяют
Знаете ли вы условия, без которых взаимная блокировка невозможна, и умеете ли по ним строить защиту. Для Android следом почти всегда спрашивают, как deadlock на главном потоке превращается в ANR и как его найти.
Как отвечать
Deadlock возникает, когда два потока или больше ждут ресурсы друг друга и ни один не может продолжить. Под «параметрами» обычно имеют в виду четыре условия Коффмана, которые должны выполняться одновременно:
- взаимное исключение: ресурсом владеет только один поток, как монитором в
synchronized; - удержание и ожидание: поток держит один замок и ждёт второй;
- неотчуждаемость: замок нельзя отобрать, его отпускает только владелец;
- циклическое ожидание: поток A ждёт замок потока B, а B ждёт замок A.
Классический пример: два потока берут те же два монитора в разном порядке. Сломать достаточно любое условие, на практике ломают последние два:
- единый порядок захвата: замки всегда берутся в одном порядке, например по id сущности;
ReentrantLock.tryLock(timeout, unit): при неудаче поток отпускает то, что держит, и повторяет попытку;- ничего чужого под замком: не вызывать колбэки и методы других объектов внутри
synchronized.
Теперь специфика Android. Если главный поток заблокирован, система через несколько секунд без ответа на ввод показывает ANR. Частые причины: runBlocking на главном потоке, когда корутина внутри ждёт Dispatchers.Main, или CountDownLatch.await() на главном потоке, пока фоновый поток постит работу в его же Handler. Найти deadlock помогает дамп потоков: трейсы ANR в Android vitals и bugreport, в обычной JVM jstack или ThreadMXBean.findDeadlockedThreads().
Где спотыкаются
Не могут назвать условия или путают их. Отсюда слабые советы вроде «добавить Thread.sleep», которые только прячут гонку.
Думают, что поток может заблокировать сам себя повторным входом в synchronized. Встроенные мониторы реентерабельны, а вот Mutex из корутин нет: вложенный withLock на том же мьютексе зависнет навсегда.
Советуют «алгоритм банкира». В прикладном коде его не применяют, он из теории операционных систем.