Можно ли атомарно проверить состояние блокировки в Java и почему это сложно
Что проверяют
Понимаете ли вы проблему «проверил, потом действую» (check-then-act): любое прочитанное состояние блокировки устаревает в момент, когда вы его получили. Следом обычно спрашивают, чем заменить такую проверку и зачем тогда вообще существует isLocked().
Как отвечать
Прочитать состояние атомарно можно, API есть. Проблема в другом: между чтением и следующим действием проходит время, за которое другой поток успевает захватить или отпустить блокировку. Результат проверки годится для мониторинга, но не для решений.
Что даёт стандартная библиотека:
ReentrantLock.isLocked(): занята ли блокировка кем-то. В документации прямо сказано, что метод предназначен для мониторинга состояния системы, а не для управления синхронизацией;isHeldByCurrentThread()иThread.holdsLock(obj): второй работает для монитораsynchronized. Оба надёжны, потому что ответ про свой поток устареть не может. Удобны вassertи отладке;getQueueLength(),hasQueuedThreads(): только оценка, очередь меняется параллельно;tryLock(): правильный ответ на вопрос. Проверка и захват здесь одна атомарная операция, поток либо получает блокировку, либо сразу узнаёт, что она занята. Есть вариант с таймаутомtryLock(long, TimeUnit);StampedLock.tryOptimisticRead()иvalidate(stamp): читаете без захвата, потом проверяете, не было ли записи за это время.
Для synchronized способа узнать, держит ли монитор чужой поток, в API нет вовсе, только снимок потоков через jstack или JFR.
Если нужен собственный флаг состояния, его меняют через AtomicBoolean.compareAndSet, а не через if над volatile.
Где спотыкаются
Пишут if (!lock.isLocked()) lock.lock(); и считают это защитой. Между двумя вызовами блокировку может занять другой поток, и проверка ничего не гарантирует.
После успешного tryLock() забывают unlock() в finally, а при неудаче, наоборот, вызывают unlock() и получают IllegalMonitorStateException.
Не знают, что tryLock() без аргументов игнорирует честность (fairness) даже у справедливой блокировки и может обойти очередь. Если порядок важен, используют tryLock(0, TimeUnit.SECONDS).