Что такое Semaphore в Java?

«Что такое Semaphore в Java?» — вопрос из категории Java Core, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Semaphore (Семафор) — это механизм синхронизации из пакета java.util.concurrent, который ограничивает количество потоков, которые могут одновременно получить доступ к общему ресурсу или пулу ресурсов. Он управляет набором виртуальных разрешений (permits).

Основные операции:

  • acquire() — Запрашивает одно разрешение. Если разрешения доступны, счетчик уменьшается, и поток продолжает работу. Если нет — поток блокируется до освобождения разрешения.
  • release() — Освобождает одно разрешение, увеличивая счетчик и потенциально разблокируя ожидающий поток.

Пример: Ограничение доступа к пулу из 3-х соединений.

import java.util.concurrent.Semaphore;

public class ConnectionPool {
    private final Semaphore semaphore = new Semaphore(3); // Макс. 3 потока одновременно

    public void useResource() {
        try {
            semaphore.acquire(); // Получаем разрешение
            // Критическая секция: работа с ограниченным ресурсом (например, сетевое соединение)
            System.out.println(Thread.currentThread().getName() + " начал работу. Свободно разрешений: " + semaphore.availablePermits());
            Thread.sleep(1000); // Имитация работы
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            semaphore.release(); // Всегда освобождаем разрешение в finally!
            System.out.println(Thread.currentThread().getName() + " закончил работу.");
        }
    }
}
// Запуск 10 потоков, но одновременно работают не более 3-х.

Важные особенности:

  • Честность (Fairness): Конструктор Semaphore(int permits, boolean fair). При fair=true разрешения предоставляются ожидающим потокам в порядке FIFO, что предотвращает голодание, но может снижать производительность.
  • Множественное приобретение/освобождение: Методы acquire(int permits) и release(int permits) работают с несколькими разрешениями сразу.
  • Попытка без блокировки: tryAcquire() возвращает false немедленно, если разрешение недоступно.

Типичные сценарии использования:

  1. Ограничение числа одновременных соединений к базе данных или внешнему API.
  2. Реализация пулов ресурсов (например, потоков, соединений).
  3. Создание ограниченных по пропускной способности шлюзов (rate limiting).

Отличие от ReentrantLock: Семафор не привязан к конкретному потоку. Разрешение, полученное одним потоком, может быть освобождено другим (хотя это не является хорошей практикой).