Definition
- Permits – a Semaphore is initialized with N permits.
acquire()decrements the count (blocks if zero).release()increments the count. - No ownership – unlike
synchronizedorReentrantLock, a semaphore permit can be released by a DIFFERENT thread than the one that acquired it. This makes it suitable for signaling patterns. - Binary semaphore –
new Semaphore(1)allows only one thread at a time, resembling a mutex. However, it is NOT reentrant (acquiring twice from the same thread causes deadlock) and has no thread ownership. - tryAcquire() – non-blocking: returns true immediately if a permit is available, false otherwise.
- tryAcquire(timeout, unit) – blocks for at most the specified timeout, then returns false if no permit became available.
- Fair mode –
new Semaphore(N, true)uses FIFO ordering: the longest-waiting thread gets the next available permit.
Think of a parking lot at National Bank of India with 5 spots. A car arrives and calls acquire(): it takes a spot (blocks if the lot is full). A car leaves and calls release(): it frees a spot. The 6th car must wait until a spot opens. A binary semaphore is a parking lot with exactly one spot, like a single-car garage.
Understanding the Concepts
What is a Semaphore?
A Semaphore is a concurrency utility that controls access to a shared resource by maintaining a count of available permits. It is commonly used to limit the number of threads that can access a resource simultaneously, such as a database connection pool or an API rate limiter.
package com.bank.concurrency;
import java.math.BigDecimal;
import java.util.concurrent.Semaphore;
public class ConnectionLimiterDemo {
private static final Semaphore dbPool = new Semaphore(5); // max 5 connections
static BigDecimal queryBalance(String accountId) throws InterruptedException {
dbPool.acquire(); // take a permit (blocks if all 5 in use)
try {
System.out.println(Thread.currentThread().getName()
+ " querying " + accountId);
Thread.sleep(100); // simulate DB query
return new BigDecimal("75000.00");
} finally {
dbPool.release(); // always release in finally!
}
}
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
final int id = i;
new Thread(() -> {
try { queryBalance("SAV" + id); }
catch (InterruptedException e) { Thread.currentThread().interrupt(); }
}, "Worker-" + i).start();
}
// 20 threads, but max 5 query DB concurrently
}
}
How is tryAcquire used for non-blocking patterns?
package com.bank.concurrency;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class NonBlockingDemo {
private static final Semaphore pool = new Semaphore(5);
static String handleRequest(String accountId) throws Exception {
// Non-blocking: return immediately if no permit
if (pool.tryAcquire()) {
try { return queryDB(accountId); }
finally { pool.release(); }
} else {
return "Service busy, try later"; // fail fast
}
}
static String handleRequestWithTimeout(String accountId) throws Exception {
// Timeout: wait up to 5 seconds for a permit
if (pool.tryAcquire(5, TimeUnit.SECONDS)) {
try { return queryDB(accountId); }
finally { pool.release(); }
} else {
throw new RuntimeException("DB pool full for 5 seconds");
}
}
static String queryDB(String id) { return "balance for " + id; }
}
How is binary semaphore different from synchronized?
| Feature | synchronized / ReentrantLock | Binary Semaphore(1) |
|---|---|---|
| Thread ownership | Yes (only owner unlocks) | No (any thread can release) |
| Reentrant | Yes (same thread re-locks) | No (re-acquire deadlocks) |
| IllegalMonitorStateException | Yes (wrong thread unlocks) | No (any release is valid) |
| Use case | Mutual exclusion | Resource limiting, signaling |
What are common mistakes?
Forgetting to release in a finally block causes permit leaks, where permits are permanently consumed and eventually all threads block.
// WRONG: no finally – permit leaked on exception
pool.acquire();
riskyOperation(); // throws exception!
pool.release(); // never reached!
// CORRECT: release in finally
pool.acquire();
try {
riskyOperation();
} finally {
pool.release(); // always released
}
What are common interview follow-up questions?
- Can you create a semaphore with zero permits? Yes. Useful for signaling: one thread waits on acquire(), another thread calls release() to signal.
- Is Semaphore fair by default? No. The default is non-fair (barging allowed). Pass true to the constructor for FIFO ordering.
Visual Overview
Semaphore(5): Limiting Concurrent Access ═════════════════════════════════════════ Permits: [1][2][3][4][5] → 5 available Thread-1: acquire() → [_][2][3][4][5] → 4 available Thread-2: acquire() → [_][_][3][4][5] → 3 available ... Thread-5: acquire() → [_][_][_][_][_] → 0 available Thread-6: acquire() → BLOCKED! (waits for a release) Thread-1: release() → [1][_][_][_][_] → 1 available Thread-6: resumes → [_][_][_][_][_] → 0 available
Key Takeaway
A Semaphore controls concurrent access by maintaining a count of permits. acquire() takes a permit and blocks if none are available. release() returns a permit. The key difference from synchronized is that a semaphore permit has no thread ownership: any thread can release, making it suitable for signaling patterns and resource limiting.
Use Semaphore for limiting concurrent access to bounded resources (database connection pools, API rate limiters, file handle pools). tryAcquire() provides non-blocking fail-fast behavior for overload scenarios. Fair mode prevents starvation when many threads compete for limited permits.
At National Bank of India, 200 threads generating reports shared a database connection pool limited to 10 connections using Semaphore(10). Without the semaphore, all 200 threads would attempt to open connections simultaneously, exhausting the pool and crashing the application. With the semaphore, exactly 10 threads queried the database at any time, and Grafana tracked availablePermits() for pool pressure alerts.
Remember one sentence: Semaphore limits concurrent access with a permit count, and unlike synchronized, any thread can release a permit, making it ideal for resource bounding and inter-thread signaling.
---