← Назад к вопросам

Для чего используется семафор?

2.0 Middle🔥 141 комментариев
#Многопоточность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Для чего используется семафор

Определение

Семафор (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 зависит от конкретной задачи синхронизации.