Что происходит при одновременном запуске двух операций с блокировкой сессии в Java?
Что проверяют
Понимаете ли вы, что вторая операция не падает, а ждёт, и умеете ли назвать, чем это ожидание ограничено. Ещё смотрят, уточните ли вы, о какой блокировке речь: внутри процесса или в базе, ведут они себя по-разному.
Как отвечать
Начните с уточнения, это нормальная реакция, и дальше разберите оба случая.
Внутри JVM вторая операция на synchronized или ReentrantLock встаёт на входе в секцию и ждёт, пока первая не отпустит монитор. Ждёт неограниченно, поток при этом занят и ничего не делает. Захват повторный для того же потока, сам себя он не заблокирует. Ограничить ожидание даёт только tryLock с таймаутом.
Пессимистичная блокировка в базе: вторая транзакция, дошедшая до тех же строк, ждёт фиксации первой. Тут ожиданием уже можно управлять: таймаутом, режимом без ожидания, пропуском занятых строк. Взаимную блокировку база находит сама и снимает одну транзакцию с ошибкой, а в JVM её не разрешит никто: потоки повиснут навсегда, и увидите вы это только в дампе потоков.
Оптимистичная блокировка ведёт себя иначе: никто не ждёт, обе операции идут параллельно, а при фиксации вторая получает ошибку по номеру версии и повторяет сценарий. Для коротких конкурирующих обновлений это обычно выгоднее.
Где спотыкаются
Считают, что synchronized защищает данные. Он защищает их в пределах одного процесса. Как только приложение запущено в двух экземплярах за балансировщиком, синхронизация превращается в украшение, и нужна общая блокировка в базе или в отдельном сервисе. Про распределённый случай спрашивают почти всегда следом.
Второе: блокируют не то и не тем. Монитор на общем объекте выстраивает в очередь вообще всех, хотя конкурируют операции по одному идентификатору. Попытка взять блокировку на строке только хуже: без интернирования объекты разные и защиты нет, а с интернированием объект общий на весь процесс. Нужен явный словарь блокировок по ключу.
Третье: не ставят границу ожидания. И захват монитора, и ожидание строки в базе длятся сколько угодно, поэтому одна медленная операция вычерпывает пул потоков, и перестаёт отвечать всё приложение. Таймаут, повтор с задержкой и понятная ошибка превращают зависание в штатный отказ.