Saturday, July 21, 2007

Java Semaphores

Java 1.5 introduced a number of concurrency libraries that build on the basic Java concurrency mechanisms (synchronized, wait/notify). A number of these libraries provide implementations of well know and understood approaches to writing multi-threaded code. One of the most useful additions is the Semaphore class.

Semaphores are a widely used concurrency mechanism, and a common approach to guarding access to limited shared resources in Operating Systems (particularly Unix). Semaphores work on the principle that only a specific number of threads (one or more) may access a shared resource at the same time.

In order to gain access to a shared resource, a thread must first acquire a lock from the semaphore. Once the lock has been obtained, the thread can use the resource. If no locks are available, they must wait until one becomes available.

Once the thread has finished using the resource, they release the lock, and notify anyone waiting for the lock that a lock is available.

The classic Semaphore works as follows:

1. A Semaphore is initialised with a specific number of locks (one or more)
2. When a thread wishes to obtain a lock, they call an acquire procedure. If the number of available locks is one or more, this will decrement the number of locks available, and return a lock to the caller. If the number of locks is less than one, the thread waits until a lock becomes available.
3. When a thread wishes to release a lock, they call a release procedure: this increases the number of locks available by one. Any waiting threads are then notified that a lock is available, and one of them (randomly selected) will acquire the lock.

The points to note regarding the implementation are as follows:

1. Once the Semaphore is initialised with a specific number of locks, there is no way to find out how many locks are available.
2. The acquire and release procedures must be atomic: i.e. a thread can not be interrupted in the middle of acquiring or releasing a lock.

It has always been fairly straight forward to implement a Semaphore in Java, and I have done so on a number of occasions. One common use is for creating a pool of objects that can be accessed by the threads in an application, such as a pool of Socket connections.
The basic Java concurrency building blocks can be utilised to fulfill the main functionality:
1. Synchronized blocks can be used to ensure the acquire and release are atomic.
2. Wait/notify can be used to implement the functionality of waiting for a lock to become available.

Although it is relatively simple to write your own Semaphore, there are obvious advantages to using a widely used library.
The following is an example of using the Semaphore class in Java. The Pool class below simulates a Pool of resources that threads wish to obtain access to. The Semaphore guards access to this Pool, to ensure that if all the resources are utilised, threads need to wait for one to be released.


import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Semaphore;

public class Pool {

private static final int NUMBER_OF_ELEMENTS = 5;

/**
* Create a Semaphore with 5 locks available
*/
private Semaphore sem = new Semaphore(NUMBER_OF_ELEMENTS);

/*
* These are the actual resources the we are restricting access to
*/
private List resources = new ArrayList();

public Pool() {
for (int i = 0; i < NUMBER_OF_ELEMENTS; i++) {
resources.add("Resource "+i);
}
}


public String getResource() {
try {
sem.acquire();

} catch (InterruptedException ex) {
}
String resource = null;
synchronized(resources) {
resource = resources.remove(0);
}
return resource;
}

public synchronized void freeResource(String resource) {
resources.add(resource);
sem.release();
}

}

The following Main class utilises this Pool by creating a number of Threads and simultaneously attempting to obtain resources. Once a resource is gained, the thread holds it for a random amount of time to simulate using the resource:


public class Main {

public Main() {
}

public static void main(String[] args) {
Pool p = new Pool();
for (int i = 0; i < 12; i++) {
Thread t = new Thread(new Client(i, p));
t.start();
}
}

private static class Client implements java.lang.Runnable {

int name;
Pool pool;

public Client(int theName, Pool thePool) {
name = theName;
pool = thePool;
}

public void run() {
String resource = pool.getResource();
System.out.println("Client " + name + " has acquired resouce " + resource);
try {
// Hold the resource for a period of time
long sleepTime = (long) (10000 * java.lang.Math.random());
java.lang.Thread.sleep(sleepTime);
} catch (InterruptedException ex) {
}
pool.freeResource(resource);
}


}

}

The Semaphore class goes beyond the original scope of the classic Semaphore. In addition to the core functionality, the following features have been provided:

1. An availablePermits()method is provided. This can be used to find out how many locks are currently available. Although this goes against the original intention of Semaphores, it is useful both for debugging, and special cases, such as a thread that does not wish to obtain a lock if only one is available.

2. A getQueueLength() method is provided. Once again, this is primarily useful for debugging or management type tools. For instance, it might be useful to expose this through a JMX interface to allow a connection pool to be monitored.

3. A tryAcquire() method is provided. This is similar to the acquire method, except if no resources are available, it does not wait. An overloaded version of this allows the caller to specify that they are prepared to wait, but only for a specific amount of time.

No comments: