Что такое Semaphore?
Semaphore — это синхронизатор из пакета java.util.concurrent, реализующий классический счётный семафор. Он управляет доступом к ограниченному числу ресурсов через внутренний счётчик разрешений (permits).
Принцип работы:
- При создании задаётся начальное количество разрешений.
acquire()— поток запрашивает разрешение. Если счётчик > 0, он уменьшается на 1 и поток проходит. Если счётчик = 0, поток блокируется до появления свободного разрешения.release()— поток возвращает разрешение, увеличивая счётчик на 1. Если есть ожидающие потоки, один из них разблокируется.
Пример — ограничение одновременных подключений к БД:
Код: пул подключений через Semaphore
class ConnectionPool {
private final Semaphore semaphore;
private final BlockingQueue<Connection> pool;
ConnectionPool(int maxConnections) {
this.semaphore = new Semaphore(maxConnections, true); // fair
this.pool = new LinkedBlockingQueue<>();
for (int i = 0; i < maxConnections; i++) {
pool.add(createConnection());
}
}
Connection acquire() throws InterruptedException {
semaphore.acquire(); // Ждём, если все разрешения заняты
return pool.poll();
}
void release(Connection conn) {
pool.offer(conn);
semaphore.release(); // Возвращаем разрешение
}
}
Основные методы:
| Метод | Описание |
|---|---|
acquire() |
Захватить одно разрешение (блокирующий) |
acquire(int n) |
Захватить n разрешений |
tryAcquire() |
Попытаться захватить без блокировки |
tryAcquire(time, unit) |
Попытаться с таймаутом |
release() |
Освободить одно разрешение |
availablePermits() |
Текущее количество свободных разрешений |
drainPermits() |
Забрать все доступные разрешения |
Fair vs Unfair. Конструктор new Semaphore(permits, true) создаёт «честный» семафор — потоки получают разрешения в порядке очереди (FIFO). По умолчанию семафор «нечестный» — это быстрее, но потоки могут получить разрешение вне очереди.
Бинарный семафор (permits = 1) — по поведению похож на мьютекс, но с важным отличием: семафор не имеет понятия «владелец» — release() может вызвать любой поток, не только тот, кто вызвал acquire(). Это отличает его от ReentrantLock, где unlock() может вызвать только поток-владелец.
Аналогия: семафор — это парковка с ограниченным числом мест. На въезде стоит счётчик свободных мест. Когда машина заезжает — счётчик уменьшается. Когда выезжает — увеличивается. Если мест нет (счётчик = 0), новые машины ждут у шлагбаума.
На собеседовании могут спросить: «Чем
Semaphoreс одним permit отличается отReentrantLock?» Ключевые отличия: семафор не реентрабельный (повторныйacquire()из того же потока заблокирует его), иrelease()может вызвать любой поток.