Saturday, 18 June 2011

Java locks - Semaphores

Semaphores ensure that only n threads can enter into the critical section of the code at a given time.

Semaphores concept was invented by the famous Dutch computer scientist Edsger Dijkstra. Basically a semaphore is a counter (integer) that allows a thread to get into a critical region if the value of the counter is greater than 0. If it’s the case, the counter is decremented by one otherwise, the thread is waiting until it can go. And when the thread go away from the critical region, the counter is incremented by one to allow one more thread to pass the critical region. A semaphore is created with a certain value for its counter. So, you can execute two actions on a semaphore P and V.

V is also known as signal and it increments the semaphore. P is also known as wait and it decrements semaphore.

By example, if you have a critical that cannot be executed concurrently, you can use a semaphore :

sem mutex = new sem(1)

P(mutex)
//Critical region
V(mutex)

So you must always call by yourself the P operation before the critical region and V after it. We call a mutex (mutual exclusion) a semaphore with a value of one. So only one thread can enter the region guarded by the semaphore. This is the most used semaphore. The other use of semaphore is to guard a set of resources like database connections or a data pool. In Java, Semaphore were introduced in jdk5. A semaphore is created using the java.util.concurrent.Semaphore class. You can create easily :
Semaphore mutex = new Semaphore(1);
Semaphore available = new Semaphore(100);

The P and V operations

As discussed above, the P and V operations are represented using the acquire and release methods. The method acquire can be interrupted if the thread is interrupted. There is an ininterruptible version with the method acquireUninterruptibly(). There is also a third version with the tryAcquire method. This method acquire a permit only if there is one permit available, otherwise, this method return false directly. All the waiting methods have also an overloaded version with a timeout. You can also acquire several permits at once using the permits argument to the different versions of acquire methods.

Examples

A little example with a mutex using the same example as the previous post on Java concurrency :
public class Counter {
private int value = 0;

private final Semaphore mutex = new Semaphore(1)

public int getNextValue() throws InterruptedException {
try {
mutex.acquire();
return value++;
} finally {
mutex.release();
}
}
}
Consider the test class:
package com.vaani.semaphores.demo;

import java.util.concurrent.Semaphore;

import com.vaani.mutex.Counter;

public class MutexDemo extends Thread{
public static void main(String[] args)
{
// Limiting No on threads running to 2
Counter counter = new Counter();
for(int i=0 ;i<5;i++){
new MutexThread(String.valueOf(i),counter ).start();
}
System.out.println("End of Semaphore Test");
}
}


class MutexThread extends Thread
{

private String name = null;
private Counter counter =null;
public MutexThread(String name,Counter counter_){

this.name = name;
this.counter = counter_;
}


public void run(){
try {
System.out.println("Counter is now "+counter.getNextValue());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

}

For more informations about Semaphore in Java, the best is to consult the Javadoc of the Semaphore class.

Simple Semaphore Example

Consider a simple semaphore in which we just acquire lock and our work and release it:
import java.util.*;
import java.util.concurrent.*;
class SimpleSemaphoreDemo
{
public static void main(String[] args)
{
// Limiting No on threads running to 2
Semaphore semaphore = new Semaphore(2);
for(int i=0 ;i<5;i++){
new MyThread(String.valueOf(i),semaphore).start();
}
System.out.println("End of Semaphore Test");
}
}


class MyThread extends Thread
{

private String name = null;
private Semaphore semaphore =null;
public MyThread(String name,Semaphore semaphore){

this.name = name;
this.semaphore = semaphore;
}


public void run(){
try{
semaphore.acquire();
System.out.println("Thread "+ name +" is start running");
sleep(500);
semaphore.release();
System.out.println("Thread "+ name +" Ends");
}catch(Exception exp){
exp.printStackTrace();
}
}

}
In this example, we have created 5 threads but no of running threads are limited to only 2, as the line :
Semaphore semaphore = new Semaphore(2); Then we are calling acquire and release functions. The output in this case will look like this :

Thread 0 is start running
End of Semaphore Test
Thread 1 is start running
Thread 0 Ends
Thread 2 is start running
Thread 1 Ends
Thread 3 is start running
Thread 3 Ends
Thread 2 Ends
Thread 4 is start running
Thread 4 Ends

If we put semaphore for 3 threads:
Semaphore semaphore = new Semaphore(3); Now the output will look like this :
Thread 0 is start running
Thread 1 is start running
Thread 2 is start running
End of Semaphore Test
Thread 4 is start running
Thread 2 Ends
Thread 3 is start running
Thread 0 Ends
Thread 1 Ends
Thread 3 Ends
Thread 4 Ends

Conclusion

To conclude, semaphores are a powerful ways to solve concurrency problems, but this is not adapted to all problems. If you need only mutual exclusion, synchronized blocks are a better solutions. The problems with semaphores is that you can forget to call the release method and that can cause deadlock sometimes difficult to find.


Download the source


Source code of this example can be downloaded from here.

No comments:

Post a Comment