Semaphore in Java

What is semaphore?

Lets first talk about definition of semaphore we have read in our Operating System books:

Semaphore is a counter (integer) value with help of which, process allows a thread to get into a critical region.

  • If the value of the counter is greater than 0, the counter is decremented by one and allowed to enter critical region. If value of counter is 0, thread keeps on waiting until counter value becomes greater than 0.
  • And when the thread exits the critical region, the counter is incremented by one to allow one more thread to pass the critical region.

So, you can execute two operations on a semaphore (counter) Enter and Exit.

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

sem mutex = new sem(1)
Enter(mutex)
//Critical region
Exit(mutex)

We call a semaphore with a value one as mutex (mutual exclusion). So only one thread can enter the region guarded by the semaphore.

Let’s talk about Java now:

In Java, 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 Enter and Exit operations are represented using the acquire and release methods.

A little example with a mutex using the same example as the previous post on Java concurrency:

public class Example {
     private int value = 0;
     private final Semaphore mutex = new Semaphore(1)
     public int getNextValue() throws InterruptedException {
         try {
             mutex.acquire();
             return value++;
         } finally {
             mutex.release();
         }
     }
 }

The method acquire can be interrupted if the thread is interrupted. There is an un-interruptible version with the method acquireUninterruptibly().(If you don’t understand this, then I am going to talk about this latter)

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.

Can we use Semaphore instead of synchronized block?

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.

How to limit resource access using Semaphore?

Semaphore allows us to ensure that only n processes can access a certain resource at a given time.

Example:

Consider we have a process that downloads resources for us periodically via HTTP. We don’t want to spam any of the hosts and at the same time, we want to limit how many connections we are making. A simple way to do this would be with a semaphore:

public class ConnectionLimiter {
   private final Semaphore semaphore;
   private ConnectionLimiter(int maxConcurrentRequests) {
     semaphore = new Semaphore(maxConcurrentRequests);
   }
   public URLConnection acquire(URL url) throws InterruptedException, IOException {
     semaphore.acquire();
     return url.openConnection();
   }
   public void release(URLConnection conn) {
     try {
       .....
     } finally {
       semaphore.release();
     }
   }
 }

The call to acquire()will block until permits are available.

The beauty of the semaphore is that it hides all the complexity of managing access control, counting permits and, of course, getting the thread-safety right.

Are there any issues/concerns with Semaphore?

The number one thing to remember is, always release what you acquire. This is done by using try..finally constructs.

Using semaphores in wrong way could lead to deadlock.

The main things that you should be careful of when using semaphores (including binary semaphores, i.e. mutexes) are:

  • Not releasing after acquire (either missing release call or an exception is thrown and there is no finally block)
  • Long held semaphores, causing thread starvation
  • Deadlocks

What if I call only release but don’t call acquire?

One interesting property of Semaphores in Java is that release doesn’t have to be called by the same thread as acquire.

Another trick is to increase the number of permits at runtime. Contrary to what you might guess, the number of permits in a semaphore isn’t fixed, and a call to release() will always increment the number of permits, even if no corresponding acquire() call was made. Note that this can also result in bugs if you are incorrectly calling release() when no acquire() was made.

What are other important methods related to Semaphores?

Finally, there are a few useful methods to be familiar with in Java’s Semaphore. The method acquireInterruptibly() will acquire a resource, reattempting if it is interrupted. This means no outside handling of InterruptedException.

The method tryAcquire() allows us to limit how long we will wait for a permit – we can either return immediately if there is no permit to obtain, or wait a specified timeout. If you somehow have known deadlocks that you can’t fix easily or track down, you could help prevent locking up processes by using tryAcquire() with suitable timeouts.

What are some possible uses for counting semaphores?

The following come to mind:

  • Limiting concurrent access to disk (this can kill performance due to competing disk seeks)
  • Thread creation limiting
  • JDBC connection pooling / limiting
  • Network connection throttling
  • Throttling CPU or memory intensive tasks

Can you give me some example of Semaphore?

Code:

public class SemaphoreProblem implements Runnable {
 

     private final Semaphore mutex = new Semaphore(1);
 

     @Override
     public void run() {
         try {
             mutex.acquire();
             String currentThread = Thread.currentThread().getName();
             System.out.println(currentThread+" acquired");
             Thread.sleep(3000);
         } catch (InterruptedException e) {
             e.printStackTrace();
         } finally {
             String currentThread = Thread.currentThread().getName();
             System.out.println(currentThread+" released");
             mutex.release();
         }
     }
 

     public static void main(String[] args) {
         SemaphoreProblem semaphoreExample = new SemaphoreProblem();
         Thread thread1 = new Thread(semaphoreExample,"t1");
         Thread thread2 = new Thread(semaphoreExample,"t2");
         thread1.start();
         thread2.start();
     }
 }

Output:

t1 acquired
t1 released
t2 acquired
t2 released

Can you write the above code using tryAcquire ?

public class SemaphoreProblem implements Runnable {
 
     private final Semaphore mutex = new Semaphore(1);
 
     @Override
     public void run() {
         boolean status = mutex.tryAcquire();
         if(status==true) {
             try {
                 String currentThread = Thread.currentThread().getName();
                 System.out.println(currentThread+" acquired");
                 Thread.sleep(3000);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             } finally {
                 String currentThread = Thread.currentThread().getName();
                 System.out.println(currentThread+" released");
                 mutex.release();
             }
         } else {
             String currentThread = Thread.currentThread().getName();
             System.out.println(currentThread+" unable to acquire");
         }
     }

     public static void main(String[] args) {
         SemaphoreProblem semaphoreExample = new SemaphoreProblem();
         Thread thread1 = new Thread(semaphoreExample,"t1");
         Thread thread2 = new Thread(semaphoreExample,"t2");
         thread1.start();
         thread2.start();
     }
 
 }

Output:

t1 acquired
t2 unable to acquire
t1 released

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s