Do you think IIT Guwahati certified course can help you in your career?
No
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.
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
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.
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
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
Purpose
Controls access to a shared resource by multiple threads
Ensures exclusive access to a shared resource
Permit Count
Can have multiple permits
Has only one lock (binary)
Ownership
Not owned by any specific thread
Owned by the thread that acquires it
Acquisition
Acquired using acquire() method
Acquired using lock() method
Release
Released using release() method
Released using unlock() method
Blocking
Threads block until a permit is available
Threads block until the mutex is available
Fairness
Can be fair or unfair (default is unfair)
Usually unfair
Timeout
Supports timed tryAcquire() methods
Supports timed tryLock() methods
Scope
Can be used for inter-process or inter-thread synchronization
Typically used for inter-thread synchronization
Resource Sharing
Allows multiple threads to access a resource simultaneously (up to the permit count)
Allows only one thread to access a resource at a time
Signaling
Can be used for signaling between threads
Not 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
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.