Mastering Java Semaphore: Controlling Concurrent Access with a Parking‑Lot Demo

This article explains the concept and internal design of Java's Semaphore, shows how to create fair or non‑fair instances, details the acquire and release operations, compares fairness strategies, and provides a complete parking‑lot example with runnable code and output.

Programmer DD
Programmer DD
Programmer DD
Mastering Java Semaphore: Controlling Concurrent Access with a Parking‑Lot Demo

What Is a Semaphore?

A semaphore is a counting synchronizer that controls access to a set of shared resources. Each acquire() decrements the permit count, blocking when the count reaches zero, while each release() increments the count, potentially unblocking waiting threads.

Internal Structure

In the JDK, java.util.concurrent.Semaphore is built on top of the AbstractQueuedSynchronizer (AQS). It contains two inner synchronizer classes:

FairSync – implements a first‑in‑first‑out acquisition order.

NonfairSync – acquires permits without checking the queue head, offering higher throughput.

Both extend the abstract Sync class, which itself extends AQS.

Constructors

Semaphore(int permits)

– creates a semaphore with the given number of permits and a non‑fair default policy. Semaphore(int permits, boolean fair) – creates a semaphore with the given permits and the specified fairness policy.

Key Methods

acquire()

public void acquire() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}

The method delegates to AQS.acquireSharedInterruptibly(int), which repeatedly invokes tryAcquireShared(int) on the concrete Sync implementation (fair or non‑fair) until the permit can be obtained or the thread is interrupted.

release()

public void release() {
    sync.releaseShared(1);
}

This calls AQS.releaseShared(int), which in turn invokes tryReleaseShared(int) on the synchronizer to add a permit and possibly wake a waiting thread.

Fair vs. Non‑fair Acquisition

Fair mode checks whether the current thread is at the head of the CLH queue before attempting to acquire a permit:

protected int tryAcquireShared(int acquires) {
    if (hasQueuedPredecessors())
        return -1; // not first, fail fast
    int available = getState();
    int remaining = available - acquires;
    if (remaining < 0 || compareAndSetState(available, remaining))
        return remaining;
    return -1;
}

Non‑fair mode skips the queue‑head check, directly attempting a CAS update:

protected int tryAcquireShared(int acquires) {
    return nonfairTryAcquireShared(acquires);
}

final int nonfairTryAcquireShared(int acquires) {
    for (;;) {
        int available = getState();
        int remaining = available - acquires;
        if (remaining < 0 || compareAndSetState(available, remaining))
            return remaining;
    }
}

Parking‑Lot Example

The following program models a parking lot with five spaces using a semaphore. Cars (threads) acquire a permit to enter, stay for a random time, then release the permit when leaving.

public class SemaphoreTest {
    public static class Parking {
        private Semaphore semaphore;
        public Parking(int count) { semaphore = new Semaphore(count); }
        public void park() throws InterruptedException {
            semaphore.acquire();
            long time = (long)(Math.random() * 10);
            System.out.println(Thread.currentThread().getName() + " enters, stays " + time + " sec");
            Thread.sleep(time * 1000);
            System.out.println(Thread.currentThread().getName() + " leaves");
        }
        public void leave() { semaphore.release(); }
    }
    public static class Car extends Thread {
        private final Parking parking;
        public Car(Parking p) { this.parking = p; }
        public void run() {
            try { parking.park(); } finally { parking.leave(); }
        }
    }
    public static void main(String[] args) {
        Parking parking = new Parking(5);
        for (int i = 0; i < 10; i++) {
            new Car(parking).start();
        }
    }
}

Running the program produces output similar to the screenshot below, showing cars waiting when permits are exhausted and proceeding as spots become free.

This demonstration illustrates how a semaphore can be used to limit concurrent access to a finite resource pool, a pattern common in server‑side Java applications.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

javaconcurrencysemaphoreAQSthread synchronizationFair vs NonfairParking Lot Example
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.