Table of contents
1.
Introduction
2.
What is Semaphore in Java?
2.1.
Java
3.
What Are the Types of Semaphore?
3.1.
1. Binary Semaphore (Mutex)
3.2.
2. Counting Semaphore
4.
How Does a Semaphore Work?
4.1.
1. Acquiring a Permit
4.2.
2. Releasing a Permit
4.3.
3. Fairness
4.4.
Java
5.
Timed Semaphore
5.1.
1. tryAcquire(long timeout, TimeUnit unit)
5.2.
2. tryAcquire(int permits, long timeout, TimeUnit unit)
6.
Semaphore vs. Mutex
7.
Implementation of Semaphore
7.1.
Java
8.
Frequently Asked Questions
8.1.
Can a semaphore have a negative permit count?
8.2.
What happens if a thread tries to release a permit it hasn't acquired?
8.3.
Can a semaphore be used for inter-process synchronization?
9.
Conclusion
Last Updated: Aug 5, 2024
Easy

Semaphores in Java

Author Riya Singh
0 upvote
Career growth poll
Do you think IIT Guwahati certified course can help you in your career?

Introduction

Semaphores are a synchronization tool used in Java to control access to shared resources by multiple threads. They help prevent race conditions & ensure that only a limited number of threads can access a resource at a time. Semaphores are commonly used in concurrent programming to manage resource allocation & avoid conflicts between threads. 

Semaphores in Java

In this article, we will learn what semaphores are, the different types of semaphores, how they work, & how to implement them in Java. We will also discuss timed semaphores & compare semaphores with mutexes.

What is Semaphore in Java?

A semaphore is a synchronization object that controls access to a shared resource by multiple threads. It acts as a gate, which allows a certain number of threads to access the resource simultaneously. The semaphore maintains a count, which represents the number of available permits. When a thread wants to access the shared resource, it must acquire a permit from the semaphore. If the count is greater than zero, the thread is allowed to proceed, & the count is decremented. If the count is zero, the thread is blocked until a permit becomes available.

In Java, the Semaphore class is part of the java.util.concurrent package. It provides two main methods: acquire() & release(). The acquire() method is used to obtain a permit from the semaphore, while the release() method is used to return a permit to the semaphore.

Let’s look at an example of how to create & use a semaphore in Java:

  • Java

Java

import java.util.concurrent.Semaphore;

public class SemaphoreExample {
private static final int MAX_THREADS = 3;
private static Semaphore semaphore = new Semaphore(MAX_THREADS);

public static void main(String[] args) {
for (int i = 1; i <= 5; i++) {
new Thread(new SemaphoreThread(i)).start();
}
}

static class SemaphoreThread implements Runnable {
private int threadNumber;

public SemaphoreThread(int threadNumber) {
this.threadNumber = threadNumber;
}

@Override
public void run() {
try {
semaphore.acquire();
System.out.println("Thread " + threadNumber + " acquired a permit.");
// Perform some task
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("Thread " + threadNumber + " released a permit.");
semaphore.release();
}
}
}
}
You can also try this code with Online Java Compiler
Run Code


Output

Thread 3 acquired a permit.
Thread 4 acquired a permit.
Thread 1 acquired a permit.
Thread 3 released a permit.
Thread 4 released a permit.
Thread 1 released a permit.
Thread 5 acquired a permit.
Thread 2 acquired a permit.
Thread 5 released a permit.
Thread 2 released a permit.


In this example, we create a semaphore with a maximum of 3 permits. We then start 5 threads, each trying to acquire a permit from the semaphore. Only 3 threads can acquire a permit at a time, while the other threads wait until a permit becomes available. After a thread finishes its task, it releases the permit, allowing another thread to acquire it.

What Are the Types of Semaphore?

There are two types of semaphores in Java:

1. Binary Semaphore (Mutex)

A binary semaphore, also known as a mutex (mutual exclusion), is a semaphore that allows only one thread to access a shared resource at a time. It has a count of either 0 or 1. When a thread acquires a permit from a binary semaphore, the count becomes 0, indicating that the resource is in use. Other threads attempting to acquire a permit will be blocked until the permit is released. Binary semaphores are commonly used for implementing locks & ensuring exclusive access to a critical section.

For example : 

Semaphore binarySemaphore = new Semaphore(1);

2. Counting Semaphore

A counting semaphore is a semaphore that allows multiple threads to access a shared resource simultaneously, up to a specified maximum count. The count represents the number of available permits. When a thread acquires a permit, the count is decremented. If the count reaches zero, subsequent threads will be blocked until permits become available. Counting semaphores is used to control access to a limited number of resources, such as database connections or thread pools.

For example : 

Semaphore countingSemaphore = new Semaphore(5);


In this example, the counting semaphore allows up to 5 threads to access the shared resource concurrently.

The choice between a binary semaphore & a counting semaphore depends on the specific requirements of the application. If exclusive access to a resource is needed, a binary semaphore is used. If multiple threads can access a resource simultaneously, but the number of threads needs to be limited, a counting semaphore is used.

How Does a Semaphore Work?

A semaphore works by maintaining a count that represents the number of available permits. The count is initialized with a specified value when the semaphore is created. Threads interact with the semaphore with the help of two main methods, which are: acquire() & release().

1. Acquiring a Permit

When a thread wants to access a shared resource, it calls the acquire() method on the semaphore. If the count is greater than zero, the thread is granted a permit, & the count is decremented. If the count is zero, indicating that no permits are available, the thread is blocked & added to a waiting queue. The thread remains blocked until a permit becomes available.

For example : 

try {
    semaphore.acquire();
    // Access the shared resource
} catch (InterruptedException e) {
    e.printStackTrace();
}

2. Releasing a Permit

After a thread has finished accessing the shared resource, it must release the permit by calling the release() method on the semaphore. This increments the count, allowing other waiting threads to acquire a permit & access the resource.

For example : 

semaphore.release();


It's important to ensure that the release() method is called within a finally block or using a try-with-resources statement to guarantee that the permit is released even if an exception occurs.

3. Fairness

By default, semaphores in Java are unfair, meaning that the order in which threads acquire permits is not guaranteed. Threads are granted permits in an arbitrary order. However, you can create a fair semaphore by passing true to the constructor, like this:

Semaphore fairSemaphore = new Semaphore(1, true);


With a fair semaphore, threads are granted permits in the order they arrived, ensuring a first-in, first-out (FIFO) order.

Let’s discuss the complete example now : 

  • Java

Java

import java.util.concurrent.Semaphore;

public class SemaphoreExample {
private static Semaphore semaphore = new Semaphore(2);

public static void main(String[] args) {
for (int i = 1; i <= 5; i++) {
new Thread(new Worker(i)).start();
}
}

static class Worker implements Runnable {
private int id;

public Worker(int id) {
this.id = id;
}

@Override
public void run() {
try {
semaphore.acquire();
System.out.println("Thread " + id + " acquired a permit.");
// Perform some work
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("Thread " + id + " released a permit.");
semaphore.release();
}
}
}
}
You can also try this code with Online Java Compiler
Run Code


Output

Thread 1 acquired a permit.
Thread 2 acquired a permit.
Thread 2 released a permit.
Thread 1 released a permit.
Thread 5 acquired a permit.
Thread 3 acquired a permit.
Thread 3 released a permit.
Thread 5 released a permit.
Thread 4 acquired a permit.
Thread 4 released a permit.


In this example, we create a semaphore with 2 permits. We start 5 worker threads, each attempting to acquire a permit. Since there are only 2 permits available, the first 2 threads acquire the permits & start working, while the remaining threads are blocked. After a thread finishes its work & releases the permit, another waiting thread can acquire the permit & start working. This process continues until all threads have completed their work.

Timed Semaphore

In addition to the regular acquire() & release() methods, the Semaphore class in Java also provides timed versions of these methods: tryAcquire(long timeout, TimeUnit unit) & tryAcquire(int permits, long timeout, TimeUnit unit). These methods allow threads to attempt to acquire a permit within a specified time limit.

1. tryAcquire(long timeout, TimeUnit unit)

This method attempts to acquire a single permit from the semaphore within the specified timeout period. If a permit is available, the thread acquires it & continues execution. If no permit becomes available within the timeout period, the method returns false, & the thread can proceed without acquiring the permit.

For example : 

try {
    if (semaphore.tryAcquire(2, TimeUnit.SECONDS)) {
        // Permit acquired within the timeout period
        // Access the shared resource
        semaphore.release();
    } else {
        // Timeout occurred, permit not acquired
        // Handle the situation accordingly
    }
} catch (InterruptedException e) {
    e.printStackTrace();
}


In this example, the thread tries to acquire a permit within a timeout of 2 seconds. If a permit is acquired within the timeout period, the thread accesses the shared resource & releases the permit. If the timeout occurs & no permit is acquired, the thread can handle the situation accordingly.

2. tryAcquire(int permits, long timeout, TimeUnit unit)

This method is similar to the previous one but allows acquiring multiple permits within the specified timeout period. If the specified number of permits are available, the thread acquires them & continues execution. If the permits do not become available within the timeout period, the method returns false.

This is an example of using tryAcquire() with multiple permits & a timeout:

try {
    if (semaphore.tryAcquire(3, 5, TimeUnit.SECONDS)) {
        // 3 permits acquired within the timeout period
        // Access the shared resource
        semaphore.release(3);
    } else {
        // Timeout occurred, permits not acquired
        // Handle the situation accordingly
    }
} catch (InterruptedException e) {
    e.printStackTrace();
}


In this example, the thread tries to acquire 3 permits within a timeout of 5 seconds. If the 3 permits are acquired within the timeout period, the thread accesses the shared resource & releases the permits. If the timeout occurs & the permits are not acquired, the thread can handle the situation accordingly.

Timed semaphores are useful in scenarios where threads should not be blocked indefinitely waiting for permits. By specifying a timeout, threads can avoid being stuck & can take alternative actions if the permits do not become available within the expected time frame.

It's important to handle InterruptedException appropriately when using timed semaphores, as the thread may be interrupted while waiting for permits.

Semaphore vs. Mutex

Feature

Semaphore

Mutex

PurposeControls access to a shared resource by multiple threadsEnsures exclusive access to a shared resource
Permit CountCan have multiple permitsHas only one lock (binary)
OwnershipNot owned by any specific threadOwned by the thread that acquires it
AcquisitionAcquired using acquire() methodAcquired using lock() method
ReleaseReleased using release() methodReleased using unlock() method
BlockingThreads block until a permit is availableThreads block until the mutex is available
FairnessCan be fair or unfair (default is unfair)Usually unfair
TimeoutSupports timed tryAcquire() methodsSupports timed tryLock() methods
ScopeCan be used for inter-process or inter-thread synchronizationTypically used for inter-thread synchronization
Resource SharingAllows multiple threads to access a resource simultaneously (up to the permit count)Allows only one thread to access a resource at a time
SignalingCan be used for signaling between threadsNot typically used for signaling

 

Implementation of Semaphore

To implement a semaphore in Java, you can use the Semaphore class provided by the java.util.concurrent package. 

For example : 

  • Java

Java

import java.util.concurrent.Semaphore;

public class SemaphoreExample {

   private static final int MAX_THREADS = 3;

   private static Semaphore semaphore = new Semaphore(MAX_THREADS);

   public static void main(String[] args) {

       for (int i = 1; i <= 5; i++) {

           new Thread(new SemaphoreWorker(i)).start();

       }

   }

   static class SemaphoreWorker implements Runnable {

       private int threadNumber;

       public SemaphoreWorker(int threadNumber) {

           this.threadNumber = threadNumber;

       }

       @Override

       public void run() {

           try {

               semaphore.acquire();

               System.out.println("Thread " + threadNumber + " acquired a permit.");

               // Perform some work

               Thread.sleep(2000);

           } catch (InterruptedException e) {

               e.printStackTrace();

           } finally {

               System.out.println("Thread " + threadNumber + " released a permit.");

               semaphore.release();

           }

       }

   }

}
You can also try this code with Online Java Compiler
Run Code


In this example:

1. We create a Semaphore object called semaphore with a maximum permit count of MAX_THREADS (set to 3).
 

2. In the main method, we start 5 threads, each executing an instance of the SemaphoreWorker class.
 

3. Inside the run method of the SemaphoreWorker class, we use semaphore.acquire() to acquire a permit from the semaphore. If the maximum permit count is reached, the thread will be blocked until a permit becomes available.

 

4. After acquiring a permit, the thread performs some work (in this example, it sleeps for 2 seconds to simulate work).
 

5. Finally, the thread releases the permit using semaphore.release() inside a finally block to ensure the permit is always released, even if an exception occurs.
 

6. The process continues until all threads have completed their work.

When you run this example, you will see output similar to the following:

Thread 1 acquired a permit.
Thread 2 acquired a permit.
Thread 3 acquired a permit.
Thread 1 released a permit.
Thread 4 acquired a permit.
Thread 2 released a permit.
Thread 5 acquired a permit.
Thread 3 released a permit.
Thread 4 released a permit.
Thread 5 released a permit.


As you can see, the semaphore limits the number of threads that can acquire permits simultaneously. In this case, only 3 threads can acquire permits at a time, while the remaining threads are blocked until permits become available.

You can adjust the maximum permit count by modifying the MAX_THREADS constant according to your requirements.

Remember to carefully manage the acquisition and release of permits to avoid issues like deadlocks or resource leaks. Always release the permits in a finally block or using a try-with-resources statement to ensure proper cleanup.

Frequently Asked Questions

Can a semaphore have a negative permit count?

No, a semaphore's permit count cannot be negative. It is always greater than or equal to zero.

What happens if a thread tries to release a permit it hasn't acquired?

If a thread tries to release a permit it hasn't acquired, it will result in an IllegalArgumentException.

Can a semaphore be used for inter-process synchronization?

Yes, semaphores can be used for both inter-thread and inter-process synchronization.

Conclusion

In this article, we learned about semaphores in Java and how they are used for synchronization and resource management. We discussed the different types of semaphores, such as binary and counting semaphores, and understood how they work by managing a count of available permits. We also explained timed semaphores, which allow threads to attempt to acquire permits within a specified time limit. Moreover, we compared semaphores with mutexes and highlighted their key differences. Finally, we saw how to implement semaphores in Java using the Semaphore class provided by the java.util.concurrent package.You can also practice coding questions commonly asked in interviews on Coding Ninjas Code360

Also, check out some of the Guided Paths on topics such as Data Structure and AlgorithmsCompetitive ProgrammingOperating SystemsComputer Networks, DBMSSystem Design, etc., as well as some Contests, Test Series, and Interview Experiences curated by top Industry Experts.

Live masterclass