Saturday, 20 May 2017

Semaphores Using Java


public class Semaphore extends Object implements Serializable

Counting Semaphore maintains a specified number of pass or permits. In order to access a shared resource, Current Thread must acquire a permit. If the permit is already exhausted by other thread then it can wait until a permit is available due to a release of a permit from a different thread.

Semaphores are often used to restrict the number of threads than can access some (physical or logical) resource.

This concurrency utility can be very useful to implement producer-consumer design pattern or implement bounded pool or resources like Thread Pool, DB Connection pool etc.

java.util.concurrent.Semaphore class represent a Counting semaphore which is initialized with a number of permits. It provides two main methods acquire() and release() for getting permits and releasing permits.

acquire() method blocks until the permit is available. Semaphore provides both blocking methods as well as unblocking method to acquire permits.
release() adds a permit, potentially releasing a blocking acquirer.

Some Scenario where Semaphore is useful
1) To implement better Database connection pooling which will block if no more connection is available instead of failing and handover Connection as soon as it’s available.
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.concurrent.Semaphore;

/**
 * Limit Maximum number of URL connection allowed through Counting Semaphore
 * @author Rajesh Dixit
 */
class URLConnectionManager {
     private final Semaphore semaphore;
     private final int DEFAULT_ALLOWEED = 10;

     URLConnectionManager(int maxConcurrentRequests) {
           semaphore = new Semaphore(maxConcurrentRequests);
     }

     URLConnectionManager() {
           semaphore = new Semaphore(DEFAULT_ALLOWEED);
     }

     public URLConnection acquire(URL url) throws InterruptedException, IOException {
           semaphore.acquire();
           return URLConnectionManager.openConnection(url);
     }

     private static URLConnection openConnection(URL url) throws IOException {
           System.out.println("open connection - START!!");

           URLConnection urlConnection = url==null?url.openConnection():null;

           System.out.println("open connection - END!!");
           return urlConnection;
     }

     public void release(URLConnection conn) throws InterruptedException {
           try {
                Thread.sleep(200);
                // clean up activity if required
                System.out.println("Release the URL !!");
           } finally {
                semaphore.release();
           }
     }
}


public class TestURLManager {
     public static void main(String[] args) throws MalformedURLException, InterruptedException, IOException {
           URLConnectionManager manager = new URLConnectionManager(3);
           for (int i = 0; i < 10; i++) {
                manager.acquire(new URL("http:\\www.google.com"));
           }

     }
}
In above case; semaphore maintains a set of permits or pass. semaphore.acquire() method blocks if necessary until the permit is available and then takes it and each semaphore.release() adds/returns a permit.

Semaphore is like a gatekeeper which keeps track of the number of visitors allowed in a building.

2) To put a bound on collection classes. By using semaphore you can implement bounded collection whose bound is specified by counting semaphore.

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
 * Bounded List using Semaphore.
 * @author Rajesh dixit
 */
public class BoundedLinkedList<T> {
    
     private List<T> list;
     private Semaphore semaphore;

     public BoundedLinkedList(final int bound) {
           list = Collections.synchronizedList(new LinkedList<T>());
           semaphore = new Semaphore(bound);
     }

     public void add(T o) throws InterruptedException {
           semaphore.acquire();
           list.add(o);
           System.out.println(" Added element :"+ o);
     }

     public void remove(T o) {
           if (list.contains(o)) {
                list.remove(o);
                semaphore.release();
                System.out.println(" Removed element :"+ o);
           }
     }

     /**
      * Test Method
      * Adds 6 elements; then waits for 5 seconds; removes one element
      */
     public static void main(String[] args) {
           final BoundedLinkedList<Integer> bll = new BoundedLinkedList<>(5);
           new Thread(new Runnable(){
                public void run(){
                     for(int i = 1; i <= 7; i++)
                           try {
                                bll.add(i);
                           } catch (InterruptedException e) {
                                e.printStackTrace();
                           }
                }
           }).start();

           try {
                TimeUnit.SECONDS.sleep(5);
           } catch (InterruptedException e) {
                e.printStackTrace();
           }

           bll.remove(4);
     }
}
Output:
Added element :1
Added element :2
Added element :3
Added element :4
Added element :5
Removed element :4
Added element :6



Important points of Counting Semaphore
1. Semaphore class supports various overloaded version of tryAquire() method which acquires the permit from semaphore only if it's available during the time of the call.

2. Another worth noting method from Semaphore is acquireUninterruptibly() which is a blocking call and wait until a permit is available.

3. We should be very careful to make sure that we are releasing after acquire which can be missed due to programming error or any exception.

4. Long held lock can cause starvation

5. release() method doesn't have to be called by the same thread which called acquire. This is an important property that we don't have with a normal mutex in Java.

6. We can increase the number of permits at runtime (we should be careful though). This is because the number of permits in a semaphore is not fixed, and call to release will always increase the number of permits, even if no corresponding acquire call was made.

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.

Fairness in semaphore
The constructor for this class optionally accepts a fairness parameter. When set false, this class makes no guarantees about the order in which threads acquire permits. In particular, barging is permitted, that is, a thread invoking acquire() can be allocated a permit ahead of a thread that has been waiting - logically the new thread places itself at the head of the queue of waiting threads. When fairness is set to 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). Note that FIFO ordering necessarily applies to specific internal points of execution within these methods. So, it is possible for one thread to invoke acquire before another, but reach the ordering point after the other, and similarly upon return from the method. Also note that the untimed tryAcquire methods do not honor the fairness setting, but will take any permits that are available.

Generally, semaphores used to control resource access should be initialized as fair, to ensure that no thread is starved out from accessing a resource. When using semaphores for other kinds of synchronization control, the throughput advantages of non-fair ordering often outweigh fairness considerations.

This class also provides convenience methods to acquire and release multiple permits at a time. Beware of the increased risk of indefinite postponement when these methods are used without fairness set true.

Memory consistency effects

Actions in a thread prior to calling a "release" method such as release() happen-before actions following a successful "acquire" method such as acquire() in another thread.


No comments:

Post a Comment

Related Posts Plugin for WordPress, Blogger...