Monday, 15 February 2016

Semaphore in Java Concurrency

Semaphore is one of the synchronization aid provided by java concurrency util in Java 5 along with other synchronization aids like CountDownLatch, CyclicBarrier, Phaser and Exchanger.

The Semaphore class present in java.util.concurrent package is a counting semaphore in which a semaphore, conceptually, maintains a set of permits. Semaphore class has two methods that make use of permits -

  • acquire() - Acquires a permit from this semaphore, blocking until one is available, or the thread is interrupted. It has another overloaded version acquire(int permits).
  • release() - Releases a permit, returning it to the semaphore. It has another overloaded method release(int permits).

Here be informed that no actual permit objects are used; the Semaphore just keeps a count of the number available and acts accordingly thus the name Counting Semaphore.

How Semaphore works

Thread that wants to access the shared resource tries to acquire a permit using acquire() method. At that time if the Semaphore's count is greater than zero thread will acquire a permit and Semaphore's count will be decremented by one.

If Semaphore's count is zero, when thread calls acquire() method, then the thread will be blocked until a permit is available.

When thread is done with the shared resource access, it can call the release() method to release the permit. That results in the Semaphore's count incremented by one.

Semaphore class constructors

  1. Semaphore(int permits) - Creates a Semaphore with the given number of permits and nonfair fairness setting.

    If a Semaphore is initialized with 5 permits that means atmost 5 threads can call the acquire method without Calling release method.

  2. Semaphore(int permits, boolean fair) - Creates a Semaphore with the given number of permits and the given fairness setting.

    When fairness is set true, the semaphore guarantees that threads invoking any of the acquire methods are selected to obtain permits in the order in which their invocation of those methods was processed (first-in-first-out; FIFO).

Binary Semaphore

A semaphore initialized to one, and which is used such that it only has at most one permit available, can serve as a mutual exclusion lock. This is more commonly known as a binary semaphore, because it only has two states: one permit available, or zero permits available. When used in this way, the binary semaphore has the property (unlike many Lock implementations), that the "lock" can be released by a thread other than the owner (as semaphores have no notion of ownership). This can be useful in some specialized contexts, such as deadlock recovery.

Semaphore Usage

  • Semaphores are often used to restrict the number of threads than can access some (physical or logical) resource.
  • Semaphore can also be used to facilitate inter-thread communication like in producer-consumer kind of scenario.

Let's see one example where Semaphore is used to control shared access. Here we have a shared counter and three threads using the same shared counter and trying to increment and then again decrement the count. So every thread should first increment the count to 1 and then again decrement it to 0.

Code without Semaphore

public class SemaphoreDemo {
 public static void main(String[] args) {
  SharedCounter counter = new SharedCounter();
  // Creating three threads
  Thread t1 = new Thread(counter, "Thread-A");
  Thread t2 = new Thread(counter, "Thread-B");
  Thread t3 = new Thread(counter, "Thread-C");
  t1.start();
  t2.start();
  t3.start();
 }
}

class SharedCounter  implements Runnable{
    private int c = 0;

    // incrementing the value
    public void increment() {
        try {
         // used sleep for context switching
            Thread.sleep(10);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        c++;
    }
    // decrementing the value
    public void decrement() {    
        c--;
    }

    public int getValue() {
        return c;
    }
    
    @Override
    public void run() {
        this.increment();
        System.out.println("Value for Thread After increment - " + Thread.currentThread().getName() + " " + this.getValue());
        this.decrement();
        System.out.println("Value for Thread at last " + Thread.currentThread().getName() + " " + this.getValue());
        
    }
}

Output

Value for Thread After increment Thread-A 1
Value for Thread at last Thread-A 1
Value for Thread After increment Thread-B 2
Value for Thread at last Thread-B 0
Value for Thread After increment Thread-C 1
Value for Thread at last Thread-C 0

Here it can be seen that output has not come right. Thread-B while incrementing is still using the value which was incremented by Thread-A.

Code with Semaphore

import java.util.concurrent.Semaphore;

public class SemaphoreDemo {
 public static void main(String[] args) {
  
  Semaphore s = new Semaphore(1);
  SharedCounter counter = new SharedCounter(s);
  // Creating three threads
  Thread t1 = new Thread(counter, "Thread-A");
  Thread t2 = new Thread(counter, "Thread-B");
  Thread t3 = new Thread(counter, "Thread-C");
  t1.start();
  t2.start();
  t3.start();
 }
}

class SharedCounter implements Runnable{
 
    private int c = 0;
    private Semaphore s;
    SharedCounter(Semaphore s){
     this.s = s;
    }
    // incrementing the value
    public void increment() {
        try {
         // used sleep for context switching
            Thread.sleep(10);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        c++;
    }
    // decrementing the value
    public void decrement() {    
        c--;
    }

    public int getValue() {
        return c;
    }
    
    @Override
    public void run() {
     try {
         // acquire method to get one permit
         s.acquire();
         this.increment();
         System.out.println("Value for Thread After increment - " + Thread.currentThread().getName() + " " + this.getValue());
         this.decrement();
         System.out.println("Value for Thread at last " + Thread.currentThread().getName() + " " + this.getValue());
         // releasing permit
         s.release();
     }
        catch (InterruptedException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
    }
}

Output

Value for Thread After increment - Thread-A 1
Value for Thread at last Thread-A 0
Value for Thread After increment - Thread-B 1
Value for Thread at last Thread-B 0
Value for Thread After increment - Thread-C 1
Value for Thread at last Thread-C 0

Now it can be seen that access is controlled using Semaphore. Semaphore acquires and releases a permit after it is done manipulating the shared resource.

Semaphore inter-thread communication example

Let's see another example where producer-consumer is implemented using semaphores. Idea is to have 2 semaphores when first is acquired release second, when second is acquired release first. That way shared resource has controlled access and there is inter-thread communication between the threads.

Remember unlike RentrantLock, Semaphore can be released by a thread other than the owner (as semaphores have no notion of ownership).

public class SemConProdDemo {

    public static void main(String[] args) {
        
        Shared s = new Shared();
        // Producer and Consumer threads
        Thread t1 = new Thread(new SemProducer(s), "Producer");
        Thread t2 = new Thread(new SemConsumer(s), "Consumer");
        t1.start();
        t2.start();    

    }
}

// Shared class used by threads
class Shared{
    int i;
    // 2 semaphores 
    Semaphore sc = new Semaphore(0);
    Semaphore sp = new Semaphore(1);
    
    public void get(){
        try {
            // acquiring consumer semaphore
            sc.acquire();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("Got - " + i);
        // releasing producer semaphore
        sp.release();
    }
    
    public void put(int i){
        try {
            // acquiring producer semaphore
            sp.acquire();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        this.i = i;
        System.out.println("Putting - " + i);
        // releasing consumer semaphore
        sc.release();
    }
}

// Producer thread
class SemProducer implements Runnable{

    Shared s;
    SemProducer(Shared s){
        this.s = s;
    }
    @Override
    public void run() {
        for(int i = 0; i < 5; i++){
            s.put(i);
        }
    }            
}

// Consumer thread
class SemConsumer implements Runnable{
    Shared s;
    SemConsumer(Shared s){
         this.s = s;
    }
    
    @Override
    public void run() {    
        for(int i = 0; i < 5; i++){
           s.get();                
        }
    }
}

output

Putting - 0
Got - 0
Putting - 1
Got - 1
Putting - 2
Got - 2
Putting - 3
Got - 3
Putting - 4
Got - 4

Source : https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Semaphore.html

That's all for this topic Semaphore in Java. If you have any doubt or any suggestions to make please drop a comment. Thanks!


Related Topics

  1. CountDownLatch in Java concurrency
  2. CyclicBarrier in Java concurrency
  3. Exchanger in Java concurrency
  4. Phaser in Java concurrency
  5. ReentrantReadWriteLock in Java
  6. Java Concurrency interview questions

You may also like -

No comments:

Post a Comment