Skip to main content
  1. Java Concurrency (java.util.concurrent)/

Semaphore

2 mins

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 #

View Source on GitHub

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.