In this tutorial, we will understand Semaphore in Java, its constructors and methods, and lock implementation using detailed examples.
Table of Contents
Java Semaphore
Semaphore is one of the techniques that implement thread synchronization. The main use of a semaphore is to control access to a shared resource using a counter variable. Using a semaphore in Java, we can restrict the number of threads that can access the shared resource. In this way, it avoids race conditions. It represents a counter which is a non-negative value that is shared among all the threads. A thread can access the resource if the counter variable is greater than 0 else it denies access. In other words, based on the number of permits that we pass during instantiation, it tracks the number of threads that can access the same.
Working of a semaphore
As we saw in the above section, a semaphore represents a counter variable that we can share among all the threads. It holds a non-negative value which means either 0 or any value greater than 0.
- First, we initialize the semaphore with the required number of permits.
- The thread then checks the condition
counter>0
. If true, it acquires the permission to the shared resource and decrements the counter variable. If false, it blocks the thread and waits for the next permit. - After the thread completes the execution of the shared resource, it releases the resource permission and increments the counter.
- If
counter=0
, then it denies permission to the shared resource.
The below flow chart will help you understand the working of a semaphore in detail.
Types of Semaphores in Java
There are different types of semaphore in Java:
- Counting semaphore: It overcomes the problem where more than one process wants to execute a critical section.
- Bounded semaphore: This contains an upper bound that denotes how many semaphores it can store.
- Timed semaphore: This allows a thread to execute for a specified time period.
- Binary semaphore: It is similar to counting semaphore but contains only binary values i.e 0 or 1.
Java Semaphore constructors
A semaphore contains 2 types of constructors as given below:
Constructor | Description |
---|---|
Semaphore(int permits) | It creates a semaphore that initializes the number of permits |
Semaphore(int permits, boolean fair) | It creates a semaphore that initializes the number of permits along with the fairness parameter |
Semaphore methods
Method | Description |
---|---|
void acquire() | Acquires a permit from this semaphore blocking until all are available |
void acquire(int permit) | Acquires the given number of permits from this semaphore blocking until all are available |
void acquireUninterruptibly() | Acquires a permit from this semaphore blocking until one is available |
void acquireUninterruptibly(int permits) | Acquires the given number of permits from this semaphore blocking until all are available |
int availablePermits() | Returns the number of currently available permits |
int drainPermits() | Acquires and returns all immediately available permits |
int getQueueLength() | Returns the number of threads waiting to acquire the permit |
boolean hasQueuedThreads() | Returns true if there are threads waiting to acquire permit |
boolean isFair() | Returns true if the semaphore has set the fairness property |
void release() | Releases the semaphore |
void release(int permits) | Releases the given number of permits to the semaphore |
boolean tryAcquire() | Acquires a permit from the semaphore only if one at a time is available |
boolean tryAcquire(int permits) | Acquires the given number of permits only if all are available |
boolean tryAcquire(long timeperiod, TimeUnit unit) | Acquires the given number of permits only if one becomes available within the specified time |
boolean tryAcquire(int permits, long timeperiod, TimeUnit unit) | Acquires the given number of permits only if all are available within the specified time |
Java Semaphore example – as a Lock
Below is an example of how we can use a Semaphore as a lock to restrict access to the shared resource. One of the thread classes increments the counter value and the other thread class decrements the counter value. Before accessing the shared resource, the thread acquires the permit using the acquire()
method. Once the execution is complete, it releases the permit using the release()
method. In this way, it allows other threads to request the permit again. The Counter class contains the shared variable that is count. You might be interested in Concurrency Utilities in Java
import java.util.concurrent.Semaphore; class Counter { static int count = 0; } class SemaphoreDemoLock extends Thread { Semaphore s; String name; SemaphoreDemoLock(Semaphore s, String name){ this.s = s; this.name = name; } public void run() { if(this.getName().equals("Thread 1")) { System.out.println(name + " started execution"); try { System.out.println(name + " waiting to acquire permit"); s.acquire(); System.out.println(name + " acquired permit"); for(int i=0;i<3;i++) { Counter.count++; System.out.println(name + ":" + Counter.count); Thread.sleep(1000); } } catch(InterruptedException e) { e.printStackTrace(); } System.out.println(name + " releasing permit"); s.release(); } else { System.out.println(name + " started execution"); try { System.out.println(name + " waiting for permit"); s.acquire(); System.out.println(name + " acquired permit"); for(int i=0;i<3;i++) { Counter.count--; System.out.println(name + ":" + Counter.count); Thread.sleep(1000); } } catch(InterruptedException e) { e.printStackTrace(); } System.out.println(name + " releasing permit"); s.release(); } } } public class SemaphoreDemo { public static void main(String[] args) throws InterruptedException { Semaphore s = new Semaphore(1); SemaphoreDemoLock sd1 = new SemaphoreDemoLock(s, "Thread 1"); SemaphoreDemoLock sd2 = new SemaphoreDemoLock(s, "Thread 2"); sd1.start(); sd2.start(); sd1.join(); sd2.join(); System.out.println("Counter value: " + Counter.count); } }
Thread 2 started execution Thread 2 waiting for permit Thread 1 started execution Thread 1 waiting for permit Thread 2 acquired permit Thread 2:-1 Thread 2:-2 Thread 2:-3 Thread 2 releasing permit Thread 1 acquired permit Thread 1:-4 Thread 1:-5 Thread 1:-6 Thread 1 releasing permit Counter value: -6
Semaphore Example
In this example, we can see how to create a Semaphore with a specified number of permits. We create a Semaphore constructor with 3 permits. We can check the available number of permits using the availablePermits()
method. A thread can acquire the permit using the acquire()
method and release it using the release()
method. We can clearly understand the lock synchronization by creating 2 different threads.
import java.util.concurrent.Semaphore; public class SemaphoreExample { static Semaphore s = new Semaphore(3); static class SampleThread extends Thread { String name = ""; SampleThread(String name){ this.name = name; } public void run() { try { System.out.println("Available number of permits for " + name + " is: " + s.availablePermits()); System.out.println(name + " waiting to acquire lock"); s.acquire(); System.out.println(name + " acquired permit"); try { for(int i=0;i<3;i++) { System.out.println(name + " executing " + ":" + " Available number of permits: " + s.availablePermits()); Thread.sleep(1000); } } finally { System.out.println(name + " releasing permit"); s.release(); System.out.println("Available number of permits for " + name + " is: " + s.availablePermits()); } } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) throws InterruptedException { System.out.println("Total number of available permits: " + s.availablePermits()); SampleThread st1 = new SampleThread("Thread 1"); st1.start(); SampleThread st2 = new SampleThread("Thread 2"); st2.start(); } }
Total number of available permits: 3 Available number of permits for Thread 1 is: 3 Available number of permits for Thread 2 is: 3 Thread 1 waiting to acquire lock Thread 2 waiting to acquire lock Thread 1 acquired permit Thread 1 executing : Available number of permits: 2 Thread 2 acquired permit Thread 2 executing : Available number of permits: 1 Thread 1 executing : Available number of permits: 1 Thread 2 executing : Available number of permits: 1 Thread 2 executing : Available number of permits: 1 Thread 1 executing : Available number of permits: 1 Thread 1 releasing permit Thread 2 releasing permit Available number of permits for Thread 1 is: 2 Available number of permits for Thread 2 is: 3
You might be interested in reading an article on Multithreading in Java