Tuesday, June 24, 2008

The constructor of Singleton and Double-checked locking

import java.util.*;
class Singleton
{
private static Singleton instance;
private Vector v;
private boolean inUse;

private Singleton()
{
v = new Vector();
v.addElement(new Object());
inUse = true;
}

public static Singleton getInstance()
{
if (instance == null) //1
instance = new Singleton(); //2
return instance; //3
}
}

It may return 2 instances in multi-thread environment. See the process below
  1. Thread 1 calls the getInstance() method and determines that instance is null at //1.

  2. Thread 1 enters the if block, but is preempted by thread 2 before executing the line at //2.

  3. Thread 2 calls the getInstance() method and determines that instance is null at //1.

  4. Thread 2 enters the if block and creates a new Singleton object and assigns the variable instance to this new object at //2.

  5. Thread 2 returns the Singleton object reference at //3.

  6. Thread 2 is preempted by thread 1.

  7. Thread 1 starts where it left off and executes line //2 which results in another Singleton object being created.

  8. Thread 1 returns this object at //3.

There are two ways to avoid this.
  • Synchronization of a getInstance().
  • Use a static field.

Here is the smaple of Synchronization:
public static synchronized Singleton getInstance()
{
if (instance == null) //1
instance = new Singleton(); //2
return instance; //3
}

Here is the sample of static:
class Singleton
{
private Vector v;
private boolean inUse;
private static Singleton instance = new Singleton();

private Singleton()
{
v = new Vector();
inUse = true;
//...
}

public static Singleton getInstance()
{
return instance;
}
}

Java programmers created the double-checked locking idiom to be used with the Singleton creation pattern to limit how much code is synchronized. However, due to some little-known details of the Java memory model, this double-checked locking idiom is not guaranteed to work.
Here is an example of "double-checked locking"
public static Singleton getInstance()
{
if (instance == null)
{
synchronized(Singleton.class) { //1
Singleton inst = instance; //2
if (inst == null)
{
synchronized(Singleton.class) { //3
inst = new Singleton(); //4
}
instance = inst; //5
}
}
}
return instance;
}

  1. Thread 1 enters the getInstance() method.

  2. Because instance is null, thread 1 enters the first synchronized block at //1.

  3. The local variable inst gets the value of instance, which is null at //2.

  4. Because inst is null, thread 1 enters the second synchronized block at //3.

  5. Thread 1 then begins to execute the code at //4, making inst non-null but before the constructor for Singleton executes. (It is not null but the instance is not fully initialized. This is the out-of-order write problem.)

  6. Thread 1 is preempted by Thread 2.

  7. Thread 2 enters the getInstance() method.

  8. Because instance is null, thread 2 attempts to enter the first synchronized block at //1. Because thread 1 currently holds this lock, thread 2 blocks.

  9. Thread 1 then completes its execution of //4.

  10. Thread 1 then assigns a fully constructed Singleton object to the variable instance at //5 and exits both synchronized blocks.

  11. Thread 1 returns instance.

  12. Thread 2 then executes and assigns instance to inst at //2.

  13. Thread 2 sees that instance is non-null, and returns it.

It may not work because the compiler may optimize the code like the code below which may still return two different instances.
synchronized(Singleton.class) {  //3
//inst = new Singleton(); //4
instance = new Singleton();
}
//instance = inst; //5


For more information, please refer to this article

No comments:

Post a Comment