Semaphore
Semaphore is a fundamental synchronizer that maintains a set of permits. Threads acquire permits before accessing a resource and release them when finished.
Source Code #
Implementation Mechanism #
Semaphore is a straightforward application of AQS in Shared Mode. The state variable in AQS represents the number of available permits. When a thread calls acquire(), it tries to decrement the state via CAS. If the state is 0, the thread is added to the wait queue.
// Shared Mode acquisition in Semaphore.Sync
protected int tryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 || compareAndSetState(available, remaining))
return remaining;
}
}
Canonical Usage #
When to use: Use Semaphore when you have a fixed-capacity resource that can be shared among multiple threads but must be bounded (e.g., a database connection pool, a network socket limit, or a fixed number of worker slots).
Common Patterns:
- Resource Bounding: Limiting the maximum number of concurrent requests to an external service.
- Binary Semaphore: A semaphore with only one permit (0 or 1), which can act like a non-reentrant lock.
- Permit Refreshing: Unlike a lock, a permit can be released by a thread that didn’t acquire it (though this is rare and should be used with caution).
// Limit concurrent database connections to 10
Semaphore dbPool = new Semaphore(10);
public void executeWithConnection() throws InterruptedException {
dbPool.acquire(); // Blocks if all 10 connections are in use
try {
// Perform database operation
} finally {
dbPool.release();
}
}
Fairness Policy #
Like ReentrantLock, Semaphore can be constructed with a fairness policy. A fair semaphore ensures that threads acquire permits in the order they arrived, preventing starvation but reducing overall throughput.