Showing posts with label Java Concurrency. Show all posts
Showing posts with label Java Concurrency. Show all posts

2008/04/24

Inter-thread coordination: wait, notify and interrupt

Just a sample code to test the inter-thread coordinating by using the object's wait and notify methods.

public class TestWaitAndNotify {
private final Object event = new Object(); private final Task1 task1 = new Task1(); private final Task2 task2 = new Task2(); private boolean stop; public TestWaitAndNotify() {
stop = false;
} public static void main(String[] args) throws InterruptedException {
TestWaitAndNotify twan = new TestWaitAndNotify(); twan.doIt();
} void doIt() throws InterruptedException {
task2.start(); task1.start(); stop = true; System.out.println("main:t [try to stop]"); synchronized (event){
System.out.println("main:t [notify all waiting threads]"); event.notifyAll();
} System.out.println("main:t [quit]");
} class Task1 extends Thread{
public void run(){
synchronized (event){
System.out.println("task1 running."); while(!stop){
System.out.println(">>>>>>>>task1 did"); event.notifyAll(); try {
System.out.println("task1 waiting."); event.wait(0); System.out.println("task1 wake");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
} System.out.println("task1 quit");
}
}
} class Task2 extends Thread{
public void run(){ synchronized (event){
System.out.println("Task2 running."); while(!stop){
try {
//Thread.sleep(10000); System.out.println("task2 waiting."); event.wait(0); System.out.println("task2 wake");
} catch (InterruptedException e) {
e.printStackTrace();
} System.out.println("<<<<<<<task2 did"); event.notifyAll();
} System.out.println("task2 quit");
} }
}
}
Conclusion:
  1. The order of threads starting is important in order to make sure the threads are waiting and notifying in sequence per our expectation.
  2. One object could be used as an event/signal to coordinate the threads behaviors by using wait and notify combination.
  3. Don't swallow or ignore the InterruptedException as you usually will do. Try to handle it properly, because someone might try to stop the blocking thread by interrupt it. Especially you are doing some common library which might be called by various unknown classes in various unknown conditions. Ignore it in your codes might cause other guy's attempts fail.
  4. Gracefully shutting down a thread by using an central control flag. However, a control thread or the main thread should try to call the coordinating object's notifyall before quit itself in order to wake up all the waiting threads and let them have a chance to shutdown gracefully.
  5. You can not always assume the call to the interrupt method of a blocking thread will work as your wish if you try to wake up it and let it quit. The probable failure case is:
    • If the blocking of thread is caused by waiting for the monitor of a synchronized method, it can not release the lock. However, if it's the case that the thread is waiting for a synchronized object, it's possible the interrupt will release the lock.
    • InterruptedException ignored by any codes is running in the thread when its interrupt is called.

2007/10/25

Singleton vs. Multiton & Synchronization

1. The classic java singleton synchronization problem

public class Singleton{
private static Singleton instance;

/** Prevents instantiating by other classes. */
private Singleton(){}

public static synchronized Singleton getInstance()
if (instance == null){
instance = new Singleton();
}
return instance;
}
}
The synchronized keyword on the getInstance() method is the evil. In a multi-thread environment, every thread will be blocked by each other when trying to grab the singleton instance. This is the classic java singleton synchronization problem.

2. The traditional solution using a static variable

public class Singleton {
private final static Singleton instance = new Singleton();

/** Prevents instantiating by other classes. */
private Singleton() {}

public static Singleton getInstance() {
return instance;
}
}
The singleton instance is initialized during the class loading time. No synchronization is necessary on the getInstance level

3. The Classic synchronized Multiton

public class Multiton{
private static final HashMap<Object, Multiton> instances = new HashMap<Object, Multiton>();

private Multiton(){}

public static Multiton getInstance(Object key){
synchronized (instances) {
// Our "per key" singleton
Multiton instance;
if ((instance = instances.get(key)) == null) {
// Lazily create instance and add it to the map
instance = new Multiton();
instances.put(key, instance);
}
return instance;
}
}
}
The Multiton pattern begins with the concept of the Singleton pattern and expands it into an object pool of keys to objects. Instead of having a single instance per runtime, the Multiton pattern ensures a single instance per key. It simplifies retrieval of shared objects in an application.

As we noticed from the above scriptlet, the access to the getInstance() method is synchronized to ensure the uniqueness of instance per key. This, again, is a performance bottleneck in multi-thread environment. The traditional 'static' solution for Singleton pattern can't be used here since the keys are passed into the Multiton on-the-fly. We have no way to predict how many keys will be passed into the Multiton during the runtime.

4. The double-checked locking solution

public class Multiton {
private static final HashMap<Object, Multiton> instances = new HashMap<Object, Multiton>();

public static Multiton getInstance(Object key) {
// Our "per key" singleton
Multiton instance;
if ((instance = instances.get(key)) == null){
synchronized(instances){
if ((instance = instances.get(key)) == null){
instance = new Multiton();
instances.put(key, instance);
}
}
}
return instance;
}
}
Yes the double-checked locking does not work for Singleton (http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html), but it does work for our Multiton pattern. Reason? We have our intermediate HashMap object sits in between. Actually we can use the HashMap to solve the Singleton double-checked locking problem as well, if we don't already have the more elegant solution using a static variable.

5. An alternative solution: Using the Java 5 ConcurrentMap

public class Multiton {
private static final ConcurrentMap<Object, Multiton> instances = new ConcurrentHashMap<Object, Multiton>();

public static Multiton getInstance(Object key) {
// Our "per key" singleton
if (instances.get(key) == null){
// Lazily create instance and try to add it to the map
Multiton instance = new Multiton();
instances.putIfAbsent(key, instance);
}
return instances.get(key);
}
}
The Java 5 ConcurrentMap.putIfAbsent(), being an atomic operation, returns the previous value associated with the key instead of the new suggested value. This ensures the uniqueness of the Multiton instance inside the ConcurrentMap.

In this solution, before a key object has been associated with a Multiton object, if two or more threads are trying to retrieve the instance with the same key object, it's possible that multiple Multiton instance will be created inside the if() block. However, the ConcurrentMap ensures that a same Multiton instance is to be returned to the caller. Extra instances are eligible for Garbage Collection as soon as the method returns. You may not want to use this solution if the creation of the Multiton instance is an expensive operation, as in certain situation.

6. Afterwords

Synchronization and double-checked locking is such an interesting classic java topic, I'm so much wanting to participating in any sort of discussion, it'll always be interesting :)

Well well... why another J2EE blog? I benefited from other people's technical blogs, and guess what, it's a good idea to contribute some of my works too. Hope it's helpful and useful, to all of your folks.