Friday, August 14, 2009

Synchronization: Java wait notify model using a monitor

One of the common problems that I have encountered in my coding career is synchronization bottle necks. This comes with using a Singleton design pattern. When one thread updates the objects and a second thread is trying to access the same set of objects you get into situations where there are stuck threads. You would want the second thread to wait till the first thread is finished updating the objects. The most common practice for java developers is use Java Synchronization. For example:

    private static List myObjects = new ArrayList();

public static synchronized void putObject(String object) {
synchronized(myObjects) {
myObjects.add(object);
}
}

public static String getObject(int index) {
synchronized(myObjects) {
return myObjects.get(index);
}
}

The problem with this approach is that all the calls are serialized, meaning they will wait for the previous call to complete. To explain it more, say the update takes 100 milliseconds and each get takes 10 milliseconds each, you will see the following:

    putObject()      - started at 0 ms
getObject()[1] - called at 1 ms
getObject()[2] - called at 1 ms
getObject()[3] - called at 1 ms
putObject() - completed at 100 ms
getObject()[1] - completed at 110 ms
getObject()[2] - completed at 120 ms
getObject()[3] - completed at 130 ms

Now you might say that why don't you remove the synchronized block from the getObject method. Obviously you can but that will lead to reading inconsistent data. In this case the getObject calls will not wait for the updates to complete and get the current data from the list, which might be invalid or stale. If you business requirement is fine with this situation you can most welcome to remove the synchronized block from the getObject method.

But to make to that the getObject call do wait for the putObject call to finish, I use a wait-notify model. In this model all the thread making a getObject call will wait for the updateObject call to complete and then process with the getObject call without waiting for the other getObject calls to complete. To depict it in action, here is what you would see when you run the same test above:

    putObject()      - started at 0 ms
getObject()[1] - called at 1 ms
getObject()[2] - called at 1 ms
getObject()[3] - called at 1 ms
putObject() - completed at 100 ms
getObject()[1] - completed at 110 ms
getObject()[2] - completed at 110 ms
getObject()[3] - completed at 110 ms

You must have noticed that all the getObject calls happen at the same time at 110 ms and not 10 ms apart.

So how do I achieve this? Using a simple monitor object. This object has a simple boolen "isBusy" flag which is used to control whether a thread is allowed to proceed or not. Here is the code for the monitor implementation:

    public class Monitor {

private boolean isBusy = false;

public synchronized void lock() {
// locks the monitor
while (true) {
if (isBusy) {
try {
wait();
} catch (InterruptedException e) {
// ignore
}
} else {
break;
}
}

isBusy = true;
}

public void checkLock() {
while (true) {
if (isBusy) {
synchronized(this) {
try {
wait();
} catch (InterruptedException e) {
// ignore
}
}
} else {
break;
}
}
}

public synchronized void releaseLock() {
isBusy = false;
this.notifyAll(); // this will release the lock
}

}

And this is the implementation of the putObject:

    private static Monitor monitor = new Monitor();

public static synchronized void putObject(String object) {
monitor.lock(); // this will lock the monitor
try {
myObjects.add(object);
} finally {
monitor.releaseLock(); // this will release lock on the monitor
}
}

And this is the implementation of the getObject:

    private static Monitor monitor = new Monitor();

public static String getObject(int index) {
monitor.checkLock(); // this will check for a lock on the monitor
return myObjects.get(index);
}

The notifyAll() call in the releaseLonc() methos will notify all thread waiting on the monitor that the lock has been released, which will allow all the thread to proceed with the execution at the same time.

No comments:

Post a Comment