A **Semaphore** in Java Concurrency is a utility that controls access to a shared resource using a set of virtual **permits**. Unlike a standard mutual exclusion lock (Mutex) which only allows one thread, a Semaphore can allow multiple threads to access a resource in parallel up to a configured limit.

Visualizing Java Semaphore permits
Real-World Analogy: An ATM Booth with Two Machines

Imagine an ATM bank vestibule that contains exactly **2 ATM machines** inside. A security guard stands outside holding exactly **2 keys (permits)**:

  • Alice arrives first. The guard hands her a key, and she enters the booth (1 permit remaining).
  • Bob arrives second. The guard hands him the second key, and he enters (0 permits remaining).
  • Charlie arrives third. Since there are no keys left, the guard makes him wait outside in a blocked queue.
  • Once Alice finished her transaction and leaves, she **returns the key (releases permit)** to the guard. The guard immediately hands it to Charlie, who can now enter.

Implementing Semaphore in Java

Using semaphore.acquire() locks a thread if no permits are available, and semaphore.release() returns a permit to the pool:

package io.practise.threadsExample;
 
import java.util.concurrent.Semaphore;
 
public class SemaphoreExample {
  // Only 2 threads can access the resource simultaneously
  static Semaphore semaphore = new Semaphore(2);
 
  public static void main(String[] args) {
      new MyAtmThread("Alice").start();
      new MyAtmThread("Bob").start();
      new MyAtmThread("Charlie").start();
  }
 
  static class MyAtmThread extends Thread {
      public MyAtmThread(String name) {
          super(name);
      }
 
      @Override
      public void run() {
          try {
              System.out.println(getName() + " is waiting to enter the ATM booth...");
              // Acquire a permit
              semaphore.acquire();
              System.out.println(getName() + " entered the booth and is doing transactions.");
              Thread.sleep(2000); // Simulate transaction processing
              System.out.println(getName() + " is leaving the ATM booth.");
          } catch (InterruptedException e) {
              e.printStackTrace();
          } finally {
              // Always release permit in finally block to prevent thread deadlocks
              semaphore.release();
          }
      }
  }
}

Conclusion

Semaphores are highly useful for rate-limiting calls to external APIs, managing database connection pools, or shielding expensive physical server resources from concurrent overload.