Semaphores

Semaphore is a java thread synchronization construct which is used to guard the shared resources by using permits . These permits are sort of counters, which allow access to the shared resource. Thus, to access the resource, a thread must be granted a permit from the semaphore.

Each call to the Semaphore's void acquire() method takes one of the available permits or blocks the calling thread when one isn't available. Each call to Semaphore's void release() method returns an available permit, potentially releasing a blocking acquirer thread.

Semaphores whose current values can be incremented past 1 are known as counting semaphores, whereas semaphores whose current values can be only 0 or 1 are known as binary semaphores or mutexes. In either case, the current value cannot be negative.

Semaphore Constructor :


  • Semaphore(int permit) : where permits is the initial number of permits available.permits is number of threads that can access shared resource at a time. If permits is 1, then only one threads that can access shared resource at a time.
  • Semaphore(int permit, boolean fair) : By setting fair to true, we ensure that waiting threads are granted a permit in the order in which they requested access.

Semaphore acquire() method : 

Semaphore has 2 different kind of acquire() method as below :

  • void acquire( ) throws InterruptedException :Acquires a permit if one is available and decrements the number of available permits by 1.If no permit is available then the current thread waits until one of the following things happen 1) some other thread calls release() method on this semaphore or, some other thread interrupts the current thread.\
  • void acquire(int permits) throws InterruptedException :Acquires permits number of permits if available and decrements the number of available permits by permits.If permits number of permits are not available then the current thread becomes dormant until  one of the following things happens -1) some other thread calls release() method on this semaphore and available permits become equal to permits or, 2) some other thread interrupts the current thread.

Semaphore release() method :


  • void release( ) :  Releases a permit and increases the number of available permits by 1.  For releasing lock by calling release() method it’s not mandatory that thread must have acquired permit by calling acquire() method.
  • void release(int permits) :   Releases permits number of permits and increases the number of available permits by permits. For releasing lock by calling release(int permits) method it’s not mandatory that thread must have acquired permit by calling acquire()/acquire(int permit) method.
Lets see an program to make the concept more clear :

SharedResources.java

public class SharedResources {
    public static int sharedValue=0;
}

IncrementThread.java
public class IncrementThread implements Runnable {

    Semaphore semaphore;

    IncrementThread(Semaphore semaphore) {
        this.semaphore = semaphore;
    }

    @Override
    public void run() {

        System.out.println(Thread.currentThread().getName() + " is waiting for the permit");

        try {
            //acquiring the semaphore permit
            semaphore.acquire();
            System.out.println(Thread.currentThread().getName() + " has got permit");
            for (int i = 0; i < 5; i++) {
                Thread.sleep(100);
                System.out.println(Thread.currentThread().getName() + 
                " has increment value to " + SharedResources.sharedValue++);
            }
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName() + " has released permit");
        //relelasing the semaphore permit after task completion is must
        semaphore.release();
    }
}

DecrementThread.java
public class DecrementThread implements Runnable {

    Semaphore semaphore;

    DecrementThread(Semaphore semaphore) {
        this.semaphore = semaphore;
    }

    @Override
    public void run() {

        System.out.println(Thread.currentThread().getName() + 
        " is waiting for the permit");

        try {
            //acquiring the semaphore lock
            semaphore.acquire();
            System.out.println(Thread.currentThread().getName() + 
             " has got permit");
            for (int i = 0; i < 5; i++) {
                Thread.sleep(100);
                System.out.println(Thread.currentThread().getName() +  
                " has decrement value to " + SharedResources.sharedValue++);
            }
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName() + " has released permit");
        //release the semaphore lock after task completion
        semaphore.release();
    }
}

SemaphoreExample.java


public class SemaphoreExample {

    public static void main(String[] args) {

        //Here we have created semaphore with 1 , which mean only one  thread can
        //have the semaphore at a time.
        Semaphore semaphore = new Semaphore(1);
        IncrementThread incrementThread = new IncrementThread(semaphore);
        new Thread(incrementThread).start();

        DecrementThread decrementThread = new DecrementThread(semaphore);
        new Thread(decrementThread).start();


    }
}

Output:
Thread-0 is waiting for the permit
Thread-1 is waiting for the permit
Thread-0 has got permit
Thread-0 has increment value to 0
Thread-0 has increment value to 1
Thread-0 has increment value to 2
Thread-0 has increment value to 3
Thread-0 has increment value to 4
Thread-0 has release permit
Thread-1 has got permit
Thread-1 has decrement value to 5
Thread-1 has decrement value to 6
Thread-1 has decrement value to 7
Thread-1 has decrement value to 8
Thread-1 has decrement value to 9
Thread-1 has release permit


As per the output we can see that first Increment thread acquire the semaphore permit and the increment the shared resource value and release the permit, then decrement thread acquire the permit and decrement the shared resources value and release the permit in the end.

Producer Consumer problem using Semaphore 

To implement the producer consumer problem using semaphore , we would need 2 different binary semaphore,lets say producer semaphore name is producerSemaphore and consumer semaphore is consumerSemaphore. Now there is a logic if any semaphore have permit value = 0 then it can not acquire lock on shared resources , it will wait some another thread to call release() method and increment the semaphore permit value. Using the same logic we will initiliase the producerSemaphore with permit value 1 and consumerSemaphore with value 0. Because producer thread has to first produce the element.

Producer gets permit by calling semaphoreProducer.acquire() and starts producing, after producing it calls semaphoreConsumer.release(). So, that consumer could get the  permit to consume.
producerSemaphore.acquire();
System.out.println("Produced : "+i);
consumerSemaphore.release();


Consumer gets permit by calling semaphoreConsumer.acquire() and starts consuming, after consuming it calls semaphoreProducer.release(). So, that producer could get the  permit to produce.
consumerSemaphore.acquire();
System.out.println("Consumed : "+i);
producerSemaphore.release();


Lets see the complete program now :

Producer.java
public class Producer implements Runnable {

    Semaphore producerSemaphore;
    Semaphore consumerSemaphore;

    Producer(Semaphore producerSemaphore, Semaphore consumerSemaphore){
        this.consumerSemaphore =consumerSemaphore;
        this.producerSemaphore=producerSemaphore;
    }


    @Override
    public void run() {
        for(int i=1;i<=5;i++){
            try{
                //producerSemaphore was initiliased with 1 ,
                // hence it start producing
                producerSemaphore.acquire();
                System.out.println("Produced : "+i);
                consumerSemaphore.release();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}

Consumer.java
public class Consumer implements Runnable {

    Semaphore producerSemaphore;
    Semaphore consumerSemaphore;

    Consumer(Semaphore producerSemaphore, Semaphore consumerSemaphore){
        this.consumerSemaphore =consumerSemaphore;
        this.producerSemaphore=producerSemaphore;
    }

    @Override
    public void run() {
        for(int i=1;i<=5;i++){
            try{
                consumerSemaphore.acquire();
                System.out.println("Consumed : "+i);
                producerSemaphore.release();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}

ProducerConsumerExample.java
public class ProducerConsumerExample {

    public static void main(String args[]){
        Semaphore producerSemaphore=new Semaphore(1);
        Semaphore cosnumerSemaphore=new Semaphore(0);

        Producer producer=new Producer(producerSemaphore,cosnumerSemaphore);
        Consumer consumer=new Consumer(producerSemaphore,cosnumerSemaphore);

        Thread producerThread = new Thread(producer, "ProducerThread");
        Thread consumerThread = new Thread(consumer, "ConsumerThread");

        producerThread.start();
        consumerThread.start();

    }
}


Output :
Produced : 1
Consumed : 1
Produced : 2
Consumed : 2
Produced : 3
Consumed : 3
Produced : 4
Consumed : 4
Produced : 5
Consumed : 5

This cover the concept of how semaphore work in Java concurrency.If you have any question or you want to add anything, then please comment below.

Comments

Popular posts from this blog

Deploy standalone Spring MVC Application in Docker Container

Refactor Code : Separate Query from Modifier

HTTP : Must known Protocol (Part 1)