What is a Semaphore and what is a typical use case? — Cracked Java
// Concurrency & Multithreading · Synchronizers: Latch, Barrier, Semaphore, Phaser
MidTheoryCodingAmazon

What is a Semaphore and what is a typical use case?

A Semaphore maintains a set of permits: acquire() takes one (blocking if none are available) and release() returns one. It is the standard tool for limiting concurrency — capping how many threads can use a resource at once, such as a bounded connection pool or a rate-limited downstream call.

Permits, not locks

A semaphore is a counter, not a mutual-exclusion lock. With N permits, up to N threads run concurrently in the guarded section. A Semaphore(1) behaves like a lock (a binary semaphore), but with a crucial difference: a semaphore has no ownership. Any thread can release() a permit it never acquired, and you can acquire in one thread and release in another. That flexibility is also a footgun — there is no reentrancy and no "wrong thread released" check.

class ConnectionPool {
    private final Semaphore permits;
    private final BlockingQueue<Connection> pool;

    ConnectionPool(int size) {
        this.permits = new Semaphore(size, true); // fair: FIFO
        this.pool = new ArrayBlockingQueue<>(size);
        // ... fill pool with `size` connections
    }

    Connection borrow() throws InterruptedException {
        permits.acquire();          // block until a slot is free
        return pool.take();
    }

    void giveBack(Connection c) {
        pool.offer(c);
        permits.release();          // hand the slot to the next waiter
    }
}

Acquire/release in finally

The cardinal rule: pair every acquire() with a release() in a finally block, or a thrown exception leaks a permit and the pool slowly starves.

permits.acquire();
try {
    callRateLimitedApi();
} finally {
    permits.release();
}

Fairness, tryAcquire, and bulk permits

  • Fairnessnew Semaphore(n, true) grants permits in FIFO order, preventing barging and starvation at the cost of throughput. The default (non-fair) is faster but can starve.
  • tryAcquire() — non-blocking, or with a timeout, so you can shed load instead of queuing forever.
  • Bulkacquire(k) / release(k) take or return several permits atomically, useful for weighted resources.

Mark your status