Semaphore

About

A Semaphore is a concurrency control mechanism used to restrict the number of threads that can access a shared resource simultaneously. It is part of the java.util.concurrent package and is particularly useful when controlling access to a limited resource such as database connections, file systems, network sockets, or thread pools.

A Semaphore is a counter-based synchronization construct that allows a maximum number of threads to access a resource at the same time.

  • A semaphore maintains a set of permits.

  • A thread must acquire a permit before proceeding.

  • If no permits are available, the thread blocks until one is released.

  • A thread must release a permit after finishing its task

Why Use a Semaphore?

  1. Control Concurrent Access – Limit the number of threads accessing a resource.

  2. Prevent Overloading – Avoid exhausting system resources like database connections.

  3. Fair Resource Allocation – Ensure fair access to shared resources.

  4. Thread Pooling – Manage worker threads efficiently.

  5. Avoiding Deadlocks & Starvation – Prevent resource starvation and improve concurrency control.

Types of Semaphores

1. Counting Semaphore

A Counting Semaphore is a semaphore that allows multiple permits, where each permit represents one unit of a shared resource. The number of available permits defines how many threads can access the resource at the same time.

How It Works?

  • When a thread wants to access the shared resource, it acquires a permit using acquire().

  • If all permits are taken, the thread blocks until a permit is available.

  • Once the thread is done using the resource, it releases the permit using release(), making it available for another thread.

Use Cases

  • Resource Pooling: Database connection pools, file system access, network socket management.

  • Rate Limiting: Controlling API requests or access to limited resources.

  • Thread Synchronization: Ensuring that only a specific number of threads execute a particular section of code.

Example Implementation

2. Binary Semaphore (Mutex)

A Binary Semaphore, also known as a Mutex (Mutual Exclusion), is a semaphore with only one permit (0 or 1). This ensures that only one thread can access the critical section at a time.

How It Works?

  • When a thread acquires the permit, the semaphore count goes to 0, preventing other threads from acquiring it.

  • Once the thread releases the permit, the count becomes 1, allowing another thread to acquire it.

  • Binary semaphores do not track ownership, meaning any thread can release a permit, which may lead to incorrect behavior.

Use Cases

  • Mutual Exclusion: Ensuring that only one thread enters a critical section at a time.

  • Alternative to Locks: Provides similar functionality to synchronized or ReentrantLock.

  • Thread Signaling: Can be used for thread coordination, where one thread signals another to proceed.

Example Implementation

Fair vs. Unfair Semaphore

Fair Semaphore (FIFO Order)

By default, Semaphore is unfair, meaning threads may not acquire permits in order. If fairness is required, pass true in the constructor to enforce FIFO order.

  • Fair semaphore prevents starvation but may reduce performance.

Unfair Semaphore (Default)

  • Threads may acquire permits out of order.

  • Higher performance but possible starvation.

Releasing More Permits Than Acquired (Over-Release Issue)

Unlike ReentrantLock or synchronized, a semaphore does not track which thread acquired a permit, meaning a thread can mistakenly release more permits than it acquired.

Semaphore vs. ReentrantLock vs. Synchronized

Feature
Semaphore
ReentrantLock
synchronized

Control

Limits threads accessing a resource

Full thread control

Automatic

Fairness

FIFO (if enabled)

FIFO (if enabled)

Always unfair

Interruptibility

No

Yes

No

Timeout Support

tryAcquire()

tryLock()

No timeout

Reentrancy

No

Yes

Yes

Condition Variables

No

Condition

wait()/notify()

Best Practices

  1. Use a semaphore for resource pooling (e.g., database connections).

  2. Avoid over-releasing permits, as it leads to incorrect behavior.

  3. Prefer tryAcquire() for non-blocking attempts.

  4. Use fair semaphores when starvation must be avoided.

  5. Use binary semaphores for simple mutual exclusion instead of locks.

Last updated