Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Для чего используется семафор
Определение
Семафор (Semaphore) — это примитив синхронизации, который использует счётчик (permit count) для управления доступом к защищённому ресурсу. Семафор ограничивает количество потоков, которые одновременно могут получить доступ к ресурсу.
Основной принцип работы
Семафор имеет два основных метода:
public class Semaphore {
// acquire() — уменьшает счётчик разрешений
// Если счётчик > 0, поток продолжает работу
// Если счётчик == 0, поток блокируется до освобождения разрешения
public void acquire() throws InterruptedException;
// release() — увеличивает счётчик разрешений
// Разбудит один из ожидающих потоков
public void release();
}
Типы семафоров
1. Двоичный семафор (Binary Semaphore)
Счётчик может быть только 0 или 1. Фактически это мьютекс (mutex).
Semaphore binarySemaphore = new Semaphore(1);
// Попытка получить доступ
binarySemaphore.acquire(); // счётчик: 1 → 0
try {
// Критическая секция — только один поток может быть здесь
System.out.println("Protected resource access");
} finally {
binarySemaphore.release(); // счётчик: 0 → 1
}
2. Счётный семафор (Counting Semaphore)
Счётчик может быть любым положительным числом. Контролирует доступ к пулу ресурсов.
// Разрешаем максимум 3 потокам одновременный доступ
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
semaphore.acquire(); // счётчик: 3, 2, 1, 0...
try {
System.out.println("Thread " + Thread.currentThread().getName() + " using resource");
Thread.sleep(2000); // Имитация работы с ресурсом
} finally {
semaphore.release(); // счётчик: 1, 2, 3...
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();
}
Основные применения
1. Управление пулом ресурсов (Resource Pool)
Ограничение количества потоков, имеющих доступ к ограниченному набору ресурсов:
class ConnectionPool {
private final Semaphore semaphore;
private final Queue<Connection> connections;
public ConnectionPool(int poolSize) {
this.semaphore = new Semaphore(poolSize);
this.connections = new LinkedList<>();
// Инициализируем poolSize соединений
}
public Connection getConnection() throws InterruptedException {
semaphore.acquire(); // Уменьшает доступные соединения
synchronized (connections) {
return connections.poll();
}
}
public void returnConnection(Connection conn) {
synchronized (connections) {
connections.offer(conn);
}
semaphore.release(); // Увеличивает доступные соединения
}
}
2. Управление пропускной способностью (Rate Limiting)
Ограничение количества запросов в единицу времени:
class RateLimiter {
private final Semaphore semaphore;
private final long resetInterval;
public RateLimiter(int maxRequests, long intervalMillis) {
this.semaphore = new Semaphore(maxRequests);
this.resetInterval = intervalMillis;
// Периодически восстанавливаем разрешения
new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
try {
Thread.sleep(resetInterval);
semaphore.release(maxRequests - semaphore.availablePermits());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}).start();
}
public void allowRequest() throws InterruptedException {
semaphore.acquire();
}
}
3. Координация между потоками
Один поток выполнил часть работы, другой может начать:
class WorkCoordinator {
private final Semaphore step1Done = new Semaphore(0);
private final Semaphore step2Done = new Semaphore(0);
void executeStep1() throws InterruptedException {
System.out.println("Step 1 started");
Thread.sleep(1000);
System.out.println("Step 1 completed");
step1Done.release(); // Сигнализируем о завершении
}
void executeStep2() throws InterruptedException {
step1Done.acquire(); // Ждём завершения Step 1
System.out.println("Step 2 started");
Thread.sleep(1000);
System.out.println("Step 2 completed");
step2Done.release();
}
}
Отличие от других примитивов
Семафор vs Mutex
// Mutex (мьютекс) — только один поток
Semaphore mutex = new Semaphore(1);
// Семафор — несколько потоков
Semaphore semaphore = new Semaphore(5);
Семафор vs CountDownLatch
// CountDownLatch — одноразовый, не может быть переиспользован
CountDownLatch latch = new CountDownLatch(1);
latch.countDown();
latch.await(); // После этого невозможно сбросить счётчик
// Семафор — переиспользуемый
Semaphore semaphore = new Semaphore(1);
semaphore.acquire();
semaphore.release(); // Можно повторить много раз
Семафор vs CyclicBarrier
// CyclicBarrier — ждёт, пока N потоков достигнут точки
CyclicBarrier barrier = new CyclicBarrier(3);
// Все три потока должны вызвать await()
// Семафор — управляет доступом к ресурсам
Semaphore semaphore = new Semaphore(3);
Практический пример: Пул потоков для задач
class TaskExecutor {
private final Semaphore semaphore;
private final ExecutorService executor;
public TaskExecutor(int maxConcurrentTasks) {
this.semaphore = new Semaphore(maxConcurrentTasks);
this.executor = Executors.newFixedThreadPool(maxConcurrentTasks);
}
public void submitTask(Runnable task) {
executor.submit(() -> {
try {
semaphore.acquire();
try {
task.run();
} finally {
semaphore.release();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
public void shutdown() {
executor.shutdown();
}
}
Заключение
Семафор — универсальный инструмент для:
- Ограничения одновременного доступа к ресурсам
- Контроля пропускной способности
- Синхронизации между потоками
Выбор между Semaphore, Mutex, CountDownLatch, CyclicBarrier зависит от конкретной задачи синхронизации.