Decision Guide: Binary vs Counting Semaphores for Concurrency Control
Learn when to select a binary semaphore for exclusive access versus a counting semaphore for throttling a pool of resources, with trade‑offs on fairness, ownership, and a practical Java implementation.
20 Aug 2026, 04:07 UTC

Decision: When to Choose Binary or Counting Semaphores
If you need to protect a critical section so that only one thread can execute it at a time, a binary semaphore (permits = 1) is sufficient. If you must limit access to a pool of identical resources—such as a fixed number of database connections, thread‑pool slots, or allowed concurrent API calls—a counting semaphore initialized with the pool size is the appropriate choice.
Comparison: Binary vs. Counting Semaphores
| Feature | Binary Semaphore | Counting Semaphore |
|---|---|---|
| Permit range | 0 or 1 | 0 … N (set at construction) |
| Typical use case | Mutual exclusion (mutex‑like) | Resource pooling / throttling |
| Ownership semantics | None; any thread may release | None; any thread may release |
| Blocking condition | Blocks when permit = 0 | Blocks when permit = 0 |
Trade‑offs
Ownership gap
Unlike a ReentrantLock, a semaphore does not track which thread holds a permit. Any thread can call release(), which enables patterns like producer‑consumer but also creates the risk of a permit being released by the wrong thread, effectively increasing the available count beyond the intended limit.
Fairness and starvation
Many implementations expose a fairness flag. When true, permits are granted in FIFO order, preventing thread starvation. Enabling fairness adds queue‑management overhead and can reduce raw throughput under high contention.
Deadlock and leak risks
The most common failure is a forgotten release(). If a thread crashes or throws an exception after acquire() but before the matching release(), the permit is lost. In a counting semaphore with a small pool, a few leaks will eventually exhaust all permits and stall the application.
Implementation: Throttling Concurrent API Calls
The following example shows how to limit outgoing HTTP requests to five concurrent calls using a counting semaphore in Java. Place the code in a class compiled with javac (Java 8 or later) and run it from the command line or inside an IDE.
import java.util.concurrent.Semaphore; public class ApiThrottler { private final Semaphore throttle; public ApiThrottler(int permits, boolean fair) { this.throttle = new Semaphore(permits, fair); } public void callApi() { try { // acquire blocks if no permits are left throttle.acquire(); // ---- begin critical section ---- performHttpRequest(); // placeholder for actual request logic // ---- end critical section ---- } catch (InterruptedException e) { // preserve interrupt status Thread.currentThread().interrupt(); } finally { // always return the permit, even after an error throttle.release(); } } private void performHttpRequest() { // Implement your HTTP client call here // For demonstration, just sleep to simulate work try { Thread.sleep(100); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); } } // Example usage public static void main(String[] args) { ApiThrottler throttler = new ApiThrottler(5, true); // Launch many worker threads for (int i = 0; i < 20; i++) { new Thread(throttler::callApi).start(); } } }Validation and Diagnostics
- Permit monitoring: Add a logging statement that prints
throttle.availablePermits()before and after eachacquire/release. If the available count never returns to the initial value after all threads finish, a permit leak is present.- Blocking test: Create a semaphore with
new Semaphore(0, false). Callacquire()from a thread and verify (via a debugger or thread dump) that the thread stays inBLOCKEDstate until another thread invokesrelease().- Saturation test: Run the sample program above with 20 threads and a permit limit of 5. Measure timestamps (e.g., using
System.nanoTime()) for the start and end ofperformHttpRequest. You should observe that no more than five requests overlap in time; the sixth request starts only after one of the first five releases its permit.If the semaphore introduces unacceptable latency or deadlocks, consider replacing it with a
ThreadPoolExecutorsized to the same pool size, which manages concurrency via worker threads rather than permit counting.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.