Что такое семафор и как он работает в Java
Что проверяют
Отличаете ли вы ограничение параллелизма от взаимного исключения. Семафор часто называют «мьютексом со счётчиком», и по ответу сразу видно, работал ли человек с ним руками: у семафора нет владельца, и из этого следует почти всё остальное.
Как отвечать
Semaphore из пакета java.util.concurrent хранит число разрешений. Метод acquire() забирает одно и блокирует поток, если свободных нет, release() возвращает разрешение обратно. Внутри это AQS в разделяемом режиме; есть справедливый режим, tryAcquire с таймаутом и захват сразу нескольких разрешений.
Типовое применение это ограничение параллелизма: не больше заданного числа одновременных обращений к внешнему API, к диску, к пулу соединений.
private final Semaphore limit = new Semaphore(10);
limit.acquire();
try {
callExternalApi();
} finally {
limit.release();
}Обратите внимание на порядок: захват стоит до try. Стоит назвать и отличие от synchronized и ReentrantLock: семафор не привязан к потоку-владельцу, разрешение может вернуть другой поток, и на этом строят сигнальные схемы между производителем и потребителем. Обратная сторона: он не реентерабелен, повторный захват в том же потоке съедает второе разрешение.
Где спотыкаются
Пишут захват внутри try, а освобождение в finally. Если поток прерван на ожидании, разрешение не получено, а finally его всё равно вернёт: счётчик тихо растёт, и ограничение через несколько таких случаев перестаёт что-либо ограничивать.
Используют семафор как обычный лок и попадают на рекурсию: вложенный вызов того же кода ждёт разрешения, которое удерживает он сам. Для взаимного исключения нужен ReentrantLock или synchronized, а не семафор на одно разрешение.
Зовут acquire() без таймаута прямо на пути запроса. Когда внешний сервис деградирует, потоки копятся в очереди семафора и выедают пул целиком. Правильнее tryAcquire с коротким ожиданием и быстрым отказом наверх.