Semaphore.release(): примеры (JAVA)
Semaphore.release(): voidОписание метода
Метод Semaphore.release() из пакета java.util.concurrent освобождает одно разрешение (permit) семафора, увеличивая число доступных разрешений. Существует также перегрузка release(int permits), которая добавляет указанное количество разрешений. Оба метода возвращают void и не блокируют вызывающий поток.
Типичные сценарии использования: управление количеством одновременно работающих потоков (пул ограниченного размера), реализация пула ресурсов, синхронизация доступа к ограниченным внешним ресурсам.
Поведение и важные детали:
- Если вызов
release()выполняется без предварительногоacquire()тем же потоком, число доступных разрешений увеличится - это законное поведение, исключений не генерируется. - Методы не привязаны к владельцу потока; семафор не отслеживает, кто брал разрешение.
- Перегрузка
release(int permits)добавляет указанное количество разрешений; при передаче отрицательного числа обычно генерируетсяIllegalArgumentException(некорректное значение). - Семафор может быть создан с флагом справедливости (fair): в справедливом режиме ожидающие потоки получают разрешения в порядке FIFO; в несчастедливом режиме порядок не гарантируется.
- Связанные методы:
acquire(),tryAcquire(),availablePermits(),drainPermits(). Методrelease()не возвращает значения.
Аргументы и возвращаемые значения:
release()- без аргументов, возвращаетvoid.release(int permits)- аргументpermitsтипаint, ожидается неотрицательное число; метод возвращаетvoid.
Замечание о корректности: семафор не поддерживает «максимум» разрешений по умолчанию: число доступных разрешений может расти произвольно при последовательных вызовах release(), если явно не ограничивать это поведение дополнительной логикой.
Короткие примеры использования
Примеры демонстрируют типовые варианты применения методов release() и release(int).
1) Пример: простое acquire/release.
import java.util.concurrent.*;
public class SimpleRelease {
public static void main(String[] args) throws Exception {
Semaphore s = new Semaphore(1);
s.acquire();
System.out.println("acquired, available=" + s.availablePermits());
s.release();
System.out.println("released, available=" + s.availablePermits());
}
}
acquired, available=0 released, available=1
2) Пример: release(int) добавляет несколько разрешений.
import java.util.concurrent.*;
public class ReleaseMultiple {
public static void main(String[] args) {
Semaphore s = new Semaphore(0);
s.release(3);
System.out.println("available=" + s.availablePermits());
}
}
available=3
3) Пример: вызов release без предварительного acquire увеличивает доступные разрешения.
import java.util.concurrent.*;
public class ReleaseWithoutAcquire {
public static void main(String[] args) {
Semaphore s = new Semaphore(1);
s.release();
System.out.println("available=" + s.availablePermits());
}
}
available=2
4) Пример: неправильный аргумент для release(int) (пример генерации исключения).
import java.util.concurrent.*;
public class ReleaseInvalid {
public static void main(String[] args) {
Semaphore s = new Semaphore(1);
s.release(-1); // некорректное значение
}
}
Exception in thread "main" java.lang.IllegalArgumentException: permits < 0
at java.base/java.util.concurrent.Semaphore.release(Semaphore.java:...)
...Похожие механизмы в Java
Вместо семафора иногда применяются другие примитивы синхронизации в зависимости от задачи:
- CountDownLatch - однократный барьер, удобен для ожидания завершения фиксированного числа событий. Подойдет, когда требуется одноразовое ожидание, но не для многократного управления доступом.
- CyclicBarrier - барьер, синхронизирующий группу потоков на каждой итерации; полезен при циклической синхронизации, но не управляет количеством одновременных исполнителей.
- Phaser - более гибкая альтернатива для многофазной синхронизации; применяется для сложных сценариев координации этапов задач.
- ReentrantLock + Condition - низкоуровневая альтернатива для реализации специфичных политик ожидания и уведомления; требует более явного управления состоянием.
- BlockingQueue - часто заменяет семафор для управления пулом ресурсов: элементы очереди соответствуют ресурсам, извлечение блокирует при пустой очереди.
Выбор между этими средствами зависит от требований: нужна ли повторяемость (CyclicBarrier), одноразовость (CountDownLatch), управление количеством параллельных исполнений (Semaphore или BlockingQueue) или тонкая логика ожидания (ReentrantLock).
Соответствующие конструкции в других языках
Краткое сравнение и примеры для популярных языков.
- Python (threading)
import threading s = threading.Semaphore(1) s.acquire() print('acquired') s.release() print('released')acquired released
Отличие: методrelease()также не проверяет владельца потока; естьBoundedSemaphore, ограничивающий максимум. - Python (asyncio)
import asyncio async def worker(sem): await sem.acquire() print('acquired') sem.release() print('released') asyncio.run(worker(asyncio.Semaphore(1)))acquired released
Отличие: методы используются в асинхронном контексте;acquire()- корутина,release()- обычный метод. - C#
using System; using System.Threading; class Program { static void Main() { var s = new SemaphoreSlim(1); s.Wait(); Console.WriteLine("acquired"); s.Release(); Console.WriteLine("released"); } }acquired released
Примечание: естьSemaphore(системный) иSemaphoreSlim(легковесный). - Go
package main import "fmt" func main() { sem := make(chan struct{}, 1) // acquire sem <- struct{}{} fmt.Println("acquired") // release <-sem fmt.Println("released") }acquired released
В Go часто используются буферизированные каналы как семафор; нет встроенного типа Semaphore в stdlib. - Kotlin (корутины)
import kotlinx.coroutines.* import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.withPermit fun main() = runBlocking { val s = Semaphore(1) s.acquire() println("acquired") s.release() println("released") }acquired released
Отличие: уkotlinx.coroutines.sync.Semaphoreметоды являются suspend-функциями для acquire; release - обычный метод. - PHP
// Требуется расширение sysvsem $sem = sem_get(ftok(__FILE__, 'a'), 1); sem_acquire($sem); echo "acquired\n"; sem_release($sem); echo "released\n";acquired released
Замечание: доступ к системным семафорам зависит от платформы и конфигурации. - Lua
-- Простой пример с флагом и очередью (псевдокод) -- Реальная реализация основана на библиотеках для потоков(зависит от используемой библиотеки)
Часто используются сторонние библиотеки или механизмы очередей. - SQL
-- В SQL нет встроенного семафора; используются блокировки таблиц или записей SELECT pg_try_advisory_lock(1);(true или false в PostgreSQL)
В реляционных СУБД применяются advisory locks или транзакционные блокировки вместо семафора.
Типичные ошибки и примеры
Частые просчеты при использовании release() и их проявления.
- Ожидание совпадения acquire/release: предположение, что
release()должен вызывать тот же поток, который сделалacquire(). Это неверно; семафор не отслеживает владельцев. Следствие: неверные проверки логики при попытке отследить «кто» освободил ресурс. - Отрицательные аргументы: вызов
release(-n)приводит к исключению. Пример:
import java.util.concurrent.*;
public class BadReleaseArg {
public static void main(String[] args) {
Semaphore s = new Semaphore(1);
s.release(-2);
}
}
Exception in thread "main" java.lang.IllegalArgumentException: permits < 0
at java.base/java.util.concurrent.Semaphore.release(Semaphore.java:...)
...
- Потеря вызова release(): забытый вызов в блоке finally приводит к утечке «захваченных» разрешений, из-за чего другие потоки блокируются.
- Неправильное ожидание порядка: в несчастливом (non-fair) семафоре порядок предоставления разрешений может отличаться от FIFO; для строгого порядка требуется флаг справедливости при создании.
- Переполнение логики: без использования ограничений число доступных разрешений может вырасти больше, чем планировалось (последовательный вызов
release()без соответствующихacquire()).
Рекомендация по поводу ошибок: вызывать release() в блоке finally после успешного acquire(), проверять входные аргументы для release(int) и при необходимости использовать BoundedSemaphore (аналогично) либо дополнительную логику для ограничения максимума.
Изменения в API и версиях
Класс java.util.concurrent.Semaphore введен начиная с Java 5 и сохраняет стабильный API: основные методы acquire, tryAcquire, release и перегрузки сохраняются без существенных изменений. В последних релизах Java не было значительных изменений в поведении release(). Разница может появляться в реализации планировщика потоков и оптимизациях JVM, но контракт методов остается прежним.
Для корутинных библиотек (например, Kotlin с kotlinx.coroutines) семафоры реализованы отдельно и имеют особенности интеграции с suspend-функциями.
Расширенные и редкие сценарии применения
Несколько продвинутых примеров использования семафора в реальных или нестандартных сценариях.
1) Реализация пула соединений с ограничением одновременных подключений.
import java.util.concurrent.*;
import java.util.*;
class Connection {}
public class ConnectionPool {
private final Semaphore sem;
private final Queue pool = new ArrayDeque<>();
public ConnectionPool(int max) {
sem = new Semaphore(max);
for (int i = 0; i < max; i++) pool.add(new Connection());
}
public Connection acquire() throws InterruptedException {
sem.acquire();
synchronized (pool) { return pool.poll(); }
}
public void release(Connection c) {
synchronized (pool) { pool.add(c); }
sem.release();
}
public static void main(String[] args) throws Exception {
ConnectionPool cp = new ConnectionPool(2);
Connection c1 = cp.acquire();
Connection c2 = cp.acquire();
System.out.println("two acquired");
// третий вызов будет ждать, пока не освободят
new Thread(() -> {
try { Connection c3 = cp.acquire(); System.out.println("third acquired"); cp.release(c3); }
catch (InterruptedException e) { }
}).start();
Thread.sleep(500);
cp.release(c1);
System.out.println("one released");
}
}
two acquired one released third acquired
Пояснение: семафор гарантирует, что одновременно используются не более N ресурсов; очередь хранения ресурсов синхронизуется отдельно.
2) Реализация простого rate limiter с помощью семафора и периодического пополнения.
import java.util.concurrent.*;
public class RateLimiter {
private final Semaphore sem;
private final ScheduledExecutorService sched = Executors.newSingleThreadScheduledExecutor();
public RateLimiter(int permitsPerSecond) {
sem = new Semaphore(permitsPerSecond);
sched.scheduleAtFixedRate(() -> {
// пополнение до permitsPerSecond
int toAdd = permitsPerSecond - sem.availablePermits();
if (toAdd > 0) sem.release(toAdd);
}, 1, 1, TimeUnit.SECONDS);
}
public boolean tryAcquire() { return sem.tryAcquire(); }
}
// Использование: проверять tryAcquire() перед выполнением операции
(вывод зависит от использования; лимитирует количество действий в секунду)
Пояснение: release(int) используется для массового добавления разрешений; важно учитывать гонки и корректно рассчитывать необходимое пополнение.
3) Справедливость: тест, демонстрирующий порядок в fair и non-fair семафорах.
import java.util.concurrent.*;
public class FairnessDemo {
public static void demo(boolean fair) throws InterruptedException {
Semaphore s = new Semaphore(1, fair);
Runnable r = () -> {
try {
long id = Thread.currentThread().getId();
System.out.println("thread " + id + " waiting");
s.acquire();
System.out.println("thread " + id + " acquired");
Thread.sleep(100);
s.release();
} catch (InterruptedException e) {}
};
for (int i = 0; i < 5; i++) new Thread(r).start();
Thread.sleep(1500);
}
public static void main(String[] args) throws Exception {
System.out.println("non-fair:");
demo(false);
System.out.println("fair:");
demo(true);
}
}
non-fair: thread 12 waiting thread 13 waiting thread 14 waiting thread 15 waiting thread 16 waiting thread 13 acquired thread 12 acquired ... fair: thread 12 waiting thread 13 waiting thread 14 waiting thread 15 waiting thread 16 waiting thread 12 acquired thread 13 acquired ...
Пояснение: в non-fair режиме порядок может меняться, в fair режиме ожидающие потоки обслуживаются по очереди.
4) Редкий сценарий: повышение разрешений для обработки burst-запросов. При необходимости разрешения можно временно увеличить с помощью release(n), затем вручную уменьшить через drainPermits() или другую логику.
// Псевдокод: увеличить допустимый параллелизм на время всплеска
sem.release(5); // временно дать больше слотов
// обработать всплеск
// затем уменьшить: sem.reducePermits(5); // protected, требует расширения класса
(результат зависит от реализации; важно учитывать thread-safety)
Замечание: метод reducePermits в стандартной реализации является защитным и применяется в специальных подклассах или при расширении класса.