Можно ли использовать ReentrantLock для решения проблем с вложенными блокировками и какие ограничения есть
Что проверяют
Понимаете ли вы, что такое реентерабельность и какую проблему она на самом деле решает. Главная ловушка вопроса: synchronized тоже реентерабелен, поэтому вложенный захват того же замка не повод переходить на ReentrantLock. Следом обычно просят показать дедлок на двух замках и объяснить, как его избежать.
Как отвечать
Разделите два случая. Если поток повторно берёт тот же замок (метод A под замком вызывает метод B под тем же замком), ReentrantLock это позволяет: он хранит владельца и счётчик захватов, getHoldCount() показывает глубину. Освободится замок только после стольких же вызовов unlock(). Но ровно так же ведёт себя монитор synchronized.
Если потоки берут разные замки в разном порядке, реентерабельность не поможет. Здесь полезны другие возможности ReentrantLock:
tryLock(timeout, unit): поток может отступить и отпустить уже взятое вместо вечного ожидания;lockInterruptibly(): ожидание можно прервать черезinterrupt();newCondition(): несколько очередей ожидания вместо одногоwait/notify;- справедливость:
new ReentrantLock(true)отдаёт замок в порядке очереди ценой пропускной способности.
Ограничения назовите явно. Освобождение ручное, поэтому unlock() всегда в finally, а Lock не реализует AutoCloseable. unlock() из чужого потока бросит IllegalMonitorStateException. У ReentrantReadWriteLock нельзя повысить блокировку чтения до записи: поток, который держит read lock и просит write lock, повиснет сам на себе. StampedLock вообще не реентерабелен.
Главное средство от дедлока всё равно архитектурное: единый порядок захвата замков.
Где спотыкаются
Говорят, что ReentrantLock нужен, потому что synchronized при повторном входе заблокирует сам себя. Это неверно.
Вызывают lock() внутри try. Если захват бросит исключение, finally попытается освободить незахваченный замок. lock() ставится перед try.
Считают ReentrantLock заведомо быстрее или медленнее synchronized. Разница обычно невелика, выбирают по возможностям. Отдельная история с виртуальными потоками: до JDK 24 synchronized закреплял их на несущем потоке, и там советовали ReentrantLock.