Monday, June 06, 2011

Java Concurrency Utilities: using Semaphore

If you haven't done much of multi-threaded programming with Java 5, I am sure when you are ask of how prevent concurrent problems when 2 threads is accessing your data, you would think of synchronizing code, by making the method synchronized.

public class SyncCounter {
    private int c = 0;

    public synchronized void increment() {
        c++;
    }

    public synchronized void decrement() {
        c--;
    }

    public synchronized int value() {
        return c;
    }
}

Perhaps, you would probably also come up with an idea, instead of synchronizing a method, you would only synchronized a block of code.

    public void increment() {
        synchronized(c) {
            c++;
        }
    }

But with the concurrency API of java 5, they have added solutions for common threads requirements. In particular, the have added "Semaphore".

A Semaphore controls access to shared resource using a counter. If the counter has a value greater than zero, then access is allowed. If it is zero, then access is denied. What the counter is counting are permits that allow access to the shared resource. Ergo, to access the resource, a thread must be granded a permit from the semaphore.

Semaphore has two constructor:

Sempahore(int num)
Semaphore(int num, boolean how)

num specifies the initial permit count. The num parameter, specifies the number of threads that can access a shared resource at any one time. If the value of num is one, then only one thread can access the resource at any one time. By setting the how to true, you can ensure that waiting threads are granted a permit in the order in which they request access.

To acquire permit, call the acquire() method, which has these two forms:

void acquire() throws InterruptedException
void acquire(int num) throws InterruptedException

To release a permit, call release(), which has these two forms:

void release()
void release(int num)

The first form releases one permit. The second form releases the number of permits.

To use a semaphore to control access to a resource, each thread that wants to use that resource must first call acquire() before accessing the resource. When the thread is done with the resource, it must call release().

import java.util.concurrent.*;

class SemaphoreDemo {

    public static void main(String args[]) {
        //instantiate a Semaphore with value 1. Meaning, 1 thread can aquire permit at a time.
        Semaphore sem = new Semaphore(1);
      
        //instantiate 2 threads to access a shared resource at the same time.
        new IncThread(sem, "A");
        new DecThread(sem, "B");
    }

}

// A shared resource.
class Shared {
    static int count = 0;
}

class IncThread implements Runnable {
    String name;
    Semaphore sem;

    IncThread(Semaphore s, String n) {
        sem = s;
        name = n;
        new Thread(this).start();
    }

    public void run() {
        try {
        //acquiring the permit.
            sem.acquire();
            System.out.println(name + "gets a permit.");
            for( int i=0; i < 5; i++ ) {
                Shared.count++;
                System.out.println(name + ":" + Shared.count);
                Thread.sleep(10);
            }
            } catch (InterruptedException exc) {
               System.out.println(exc);
            }
        //releasing the permit.
        System.out.println(name + "releases the permit.");
        sem.release();
    }
}

class DecThread implements Runnable {
    String name;
    Semaphore sem;

    DecThread(Semaphore s, String n) {
        sem = s;
        name = n;
        new Thread(this).start();
    }

    public void run() {
        try {
            sem.acquire();
            System.out.println(name + "gets a permit.");
            for(int i=0; i < 5; i++ ) {
                Shared.count--;
               System.out.println(name + ":" + Shared.count);
            }
        } catch (InterruptedException exc) {
            System.out.println(exc);
        }
        System.out.println(name + "releases the permit.");
        sem.release();
    }
}


Notice that in the run methods, there are no synchronized keywords define. That is because, the semaphore actually implements it internally making it synchronized as you acquire for the lock.

Without the use of Semaphore, accesses to Shared.count by both threads would have occurred simultaneously, and the increments and decrements would be intermixed.