Что такое Semaphore в 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: Семафор не привязан к конкретному потоку. Разрешение, полученное одним потоком, может быть освобождено другим (хотя это не является хорошей практикой).

Ответ 18+ 🔞

Так, слушай, вот эта штука — Semaphore, она же семафор, блядь. Представь себе, что у тебя есть, например, три ебучки на районе, а желающих — дохуя. Вот семафор — это такой суровый барин с палкой, который стоит и говорит: «Не-не-не, больше трёх не влезет, остальные ждите нахуй, пока кто-то не выйдет».

По сути, это просто счётчик разрешений из пакета java.util.concurrent. Разрешений (permits) — ограниченное количество. Хочешь зайти в «критическую секцию» — отдай разрешение. Выходишь — верни, чтобы следующий мог зайти.

Основные движухи:

  • acquire() — Это типа «дай мне пропуск, я готов». Если пропуск есть — забираешь и идёшь работать. Если нет — стоишь и тупишь, как мудак, пока кто-то не вылезет и не отдаст свой.
  • release() — Это «всё, я отъебался, забирайте мой пропуск». После этого счётчик увеличивается, и какой-нибудь ждущий поток может, наконец, проснуться и зайти.

Вот смотри, как это выглядит в деле. Допустим, у нас пул из 3-х соединений, а потоков — 10.

import java.util.concurrent.Semaphore;

public class ConnectionPool {
    private final Semaphore semaphore = new Semaphore(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, то разрешения будут раздавать по очереди, как в советской поликлинике — кто первый пришёл, тот первый и получил. Это честно, но медленнее. Если false — кто как успеет, может, и проскочишь.
  • Жадность: Можно сразу несколько разрешений брать или отдавать — acquire(5), release(5). Например, если один ресурс требует для работы аж 5 «пропусков».
  • Попытка без унижения: tryAcquire() — это типа «дай пропуск, но если нет — то и похуй, я не буду тут стоять, я пойду другим делом займусь». Возвращает true/false сразу.

Где это, блядь, применять?

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

Чем не ReentrantLock? А тем, что семафор — он безликий. Пропуск взял один поток, а отдать может вообще другой, хотя так делать — моветон, конечно. А лок привязан к потоку: какой поток его взял, тот и должен отпустить.