Как возникают deadlock в Java и какие параметры вызывают блокировку
Что проверяют
Знаете ли вы четыре условия взаимной блокировки и умеете ли найти её в живом процессе. Следом обычно дают код с двумя вложенными synchronized и просят исправить или спрашивают, как вы диагностировали deadlock на проде.
Как отвечать
Deadlock возникает, только когда одновременно выполнены четыре условия Коффмана:
- взаимное исключение: ресурс, например монитор объекта или
ReentrantLock, в каждый момент держит один поток; - удержание и ожидание: поток держит одну блокировку и ждёт другую;
- нет вытеснения: забрать монитор у потока нельзя, он отпустит его только сам;
- циклическое ожидание: поток A держит
lock1и ждётlock2, поток B держитlock2и ждётlock1.
Разрывают обычно последние два. Единый порядок захвата, например по id счёта при переводе денег, убирает цикл. ReentrantLock.tryLock(timeout, unit) позволяет отступить и отпустить уже взятое, чего synchronized не умеет.
Источники в Java помимо вложенных synchronized:
- чужой код под блокировкой: колбэк или слушатель внутри
synchronizedлезет в другой объект со своим замком; - голодание пула: задача в
Executors.newFixedThreadPool(n)ждётfuture.get()подзадачи в том же пуле, а свободных потоков нет; - инициализация классов: статические инициализаторы двух классов ссылаются друг на друга из разных потоков;
- база данных: транзакции обновляют строки в разном порядке, СУБД находит цикл и откатывает одну из них.
Диагностика: jcmd <pid> Thread.print или jstack выводят «Found one Java-level deadlock» и показывают, кто какой монитор держит. Программно это ThreadMXBean.findDeadlockedThreads().
Где спотыкаются
Называют четвёртым условием что-то расплывчатое вместо циклического ожидания, хотя именно его проще всего разорвать.
Путают deadlock с livelock. При livelock потоки заняты и бесконечно уступают друг другу, а в дампе нет взаимных блокировок.
Надеются, что JVM разорвёт цикл сама, как это делает база. Потоки в deadlock висят вечно, а interrupt() не выводит поток из ожидания входа в synchronized, для этого нужен lockInterruptibly().