Semaphore in Java – Working, Types and Implementation
In this article, we will discuss Semaphores in Java. We use Semaphores in the Thread Synchronization in Java. We will study the working of semaphores and types of Semaphores with examples.
There is a Semaphore class that has many constructors and methods that control access over shared resources or concurrent threads that want to access the specified resource.
Let’s begin studying the Semaphore in Java.
Semaphore in Java
A Semaphore in Java is a Thread Synchronization construct that controls access to the shared resource with the help of counters.
A semaphore also sends the signals between the threads so that the missed signals are not avoided. A semaphore is a kind of variable that manages the concurrent processes and synchronizes them.
We can use a semaphore to avoid race conditions. A semaphore can restrict the number of threads to access a shared resource.
For example, a semaphore can limit a maximum of 10 connections to access a shared file simultaneously.
Working of a Semaphore
We know that a semaphore controls access over the shared resource with the help of counters. The counter has some value.
If the value of the counter is greater than zero, then the thread gets permission to access the resource and then the counter value decrements.
And if the value of the counter is zero, then the thread does not get access to the resource.
When the thread completes its execution inside the resources or no longer needs the resource then it releases the permit and the value of the counter gets incremented.
Flow chart of Semaphore:
The following figure shows the working of the semaphore using a flow chart:
The Semaphore class in Java
Java provides the Semaphore class to implement the above mechanism of semaphore. This Semaphore class is present in the java.util.concurrent package.
The Semaphore class provides all the functionalities on its own, so there is no need to implement it manually.
Constructors of Semaphore class:
There are two constructors of the Semaphore class. They are:
1. Semaphore(int num)
2. Semaphore(int num, boolean how)
Here, num represents the number of threads that can access the shared resource. The boolean value is set to true so that it permits waiting threads in the order they requested the access.
Methods of Semaphore class:
There are following methods in Semaphore class:
1. acquire(): This method returns true if a permit is available immediately and acquires it, otherwise, returns false, but acquire() acquires a permit and blocking until one is available.
2. release(): The release() method releases a permit.
3. availablePermits(): This method returns the number of current permits available.
How to Implement Semaphore in Java
package com.techvidvan.semaphore; import java.util.concurrent. * ; //A shared resource/class. class SharedResource { static int count = 0; } class MyThread extends Thread { Semaphore semaphore; String threadName; public MyThread(Semaphore semaphore, String threadName) { super(threadName); this.semaphore = semaphore; this.threadName = threadName; }@Override public void run() { if (this.getName().equals("Thread1")) { System.out.println("Starting " + threadName); try { // First, get a permit. System.out.println(threadName + " is waiting for a permit."); //Acquiring the lock semaphore.acquire(); System.out.println(threadName + " gets a permit."); // Accessing the shared resource. // other waiting threads will wait, until this thread releases the lock for (int i = 0; i < 5; i++) { SharedResource.count++; System.out.println(threadName + ": " + SharedResource.count); //Allowing a context switch if possible.for thread B to execute Thread.sleep(10); } } catch(InterruptedException exc) { System.out.println(exc); } // Release the permit. System.out.println(threadName + " releases the permit."); semaphore.release(); } // Run by thread B else { System.out.println("Starting " + threadName); try { // First, get a permit. System.out.println(threadName + " is waiting for a permit."); // acquiring the lock semaphore.acquire(); System.out.println(threadName + " gets a permit."); // Now, accessing the shared resource. // other waiting threads will wait, until this thread release the lock for (int i = 0; i < 5; i++) { SharedResource.count--; System.out.println(threadName + ": " + SharedResource.count); Thread.sleep(10); } } catch(InterruptedException exc) { System.out.println(exc); } // Release the permit. System.out.println(threadName + " releases the permit."); semaphore.release(); } } } //Main class public class SemaphoreDemo { public static void main(String args[]) throws InterruptedException { // Creating a Semaphore object with number of permits = 1 Semaphore semaphore = new Semaphore(1); // Creating two threads with name t1 and t2 // Note that thread A will increment the count and thread B will decrement the count MyThread t1 = new MyThread(semaphore, "Thread1"); MyThread t2 = new MyThread(semaphore, "Thread2"); t1.start(); t2.start(); // waiting for threads t1 and t2 t1.join(); t2.join(); //count will always be 0 after both threads complete their execution System.out.println("count: " + SharedResource.count); } }
Output:
Starting Thread1
Starting Thread2
Thread1 is waiting for a permit.
Thread2 is waiting for a permit.
Thread1 gets a permit.
Thread1: 1
Thread1: 2
Thread1: 3
Thread1: 4
Thread1: 5
Thread1 releases the permit.
Thread2 gets a permit.
Thread2: 4
Thread2: 3
Thread2: 2
Thread2: 1
Thread2: 0
Thread2 releases the permit.
count: 0
Note: The output can be different in different executions of the above program, but the final value of the count variable will always remain 0.
Explanation of the above program :
- The program uses a semaphore to control access to the count variable, which is a static variable within the Shared class. Shared.count increments five times by thread t1 and decrements five times by thread t2. To prevent these two threads from accessing Shared.count at the same time, access is allowed only after getting a permit from the controlling semaphore. After access is complete, the permit releases. Using this way, only one thread at a time will access Shared.count, as we can see in the output.
- Notice the call to sleep( ) within run( ) method inside MyThread class. We use it to “prove” that the semaphore synchronizes the accesses to Shared.count. In run() method, the call to sleep() method invokes the thread to pause between each access to Shared.count. This would enable the second thread to run. However, because of the semaphore, the second thread must wait until the first has released the permit, which happens only after all accesses by the first thread are complete. Thus, Shared.count is first incremented five times by thread t1 and then decremented five times by thread t2. The increments and decrements are not intermixed at assembly code.
- Without the use of the semaphore, accesses to Shared.count by both threads would occur simultaneously, and there could be a mixture of increments and decrements.To confirm this, try commenting out the calls to acquire( ) and release( ). When you run the program, you will see that access to Shared.count is no longer synchronized, thus you will not always get count value 0.
Types of Semaphore
There are 4 types of Semaphores:
1. Counting Semaphores
There are some cases when more than one process wants to execute in a critical section simultaneously, so the counting semaphores are useful to achieve this.
The Semaphore implementation takes place through the take() method.
Counting Semaphore in Java Example:
public class CountingSemaphore { private int signals = 0; public synchronized void take() { this.signals++; this.notify(); } public synchronized void release() throws InterruptedException { while(this.signals == 0) wait(); This.signals--; } }
2. Bounded Semaphores
The counting semaphores do not have any upper bound values that how many signals it can store. So the use of bounded semaphores is to set the upper bound.
public class BoundedSemaphore { private int signal = 0; private int bound = 0; public BoundedSemaphore(int upperBound) { this.bound = upperBound; } public void synchronized take() throws InterruptedException { while(this.signal == bound) wait(); this.signal++; this.notify++; } public void synchronized release() throws InterruptedException { while(this.signal == 0) wait(); this.signal--; } }
3. Timed Semaphores
The Timed Semaphore is a semaphore that allows a thread to run for a particular duration or a period of time. After this period of time, the timer resets, and all the permits release.
4. Binary Semaphores
The Binary semaphores are quite similar to the counting semaphores, but they can take only binary values i.e., either 0 or 1.
It is easier to implement the binary semaphore than the counting semaphore. If the value = 1, then the signal operation succeeds, otherwise it fails, if the value is 0.
Using Semaphores as Locks
We can also use a bounded semaphore as a lock. For this, we have to set the upper bound to 1 and need to call the take() and release() methods to guard the critical section.
Here is an example:
BoundedSemaphore semaphore = new BoundedSemaphore(1); ... semaphore.take(); try { //critical section } finally { semaphore.release(); }
Conclusion
Here we come to the end of the article on Java Semaphores. We explored the basics of Semaphores with their working and types with the examples.
We hope that you would now have understood the concept of semaphores with implementation.