Thursday, December 1, 2011

Singletons in Java - An Analysis

The "classic" practical example...
import java.io.PrintStream;

public class LogManager {
 private static LogManager instance;
 private PrintStream pStream;

 private LogManager(PrintStream out) {
  pStream = out;
 }

 public static LogManager getInstance() {
  if (instance == null)
   instance = new LogManager(System.out);
  return instance;
 }

 public void log(String msg) {
  pStream.println(msg);
 }
}


Usage: LogManager.getInstance().log( "some message" );

Problem: It's possible in a naive implementation for one thread to preempt after the test for null but before the instance creation, so that the first thread (which has already tested for null) calls the constructor again and creates another instance.

Solution: Synchronize the getinstance method
public static synchronized LogManager getInstance() {
  if (instance == null)
   instance = new LogManager(System.out);
  return instance;
 }

Problem : Performance overhead because of the use of synchronized keyword in method signature

Solution : Just synchronize the instance creation rather than the entire method. Double-Checked Locking is widely cited and used as an efficient method for implementing lazy initialization in a multithreaded environment.
public static LogManager getInstance() {
  if (instance == null) {
   synchronized (LogManager.class) {
    if (instance == null)
     instance = new LogManager(System.out);
   }
  }
  return instance;
 }

Problem : It will not work reliably in a platform independent way when implemented in Java. The code just doesn't works in the presence of either optimizing compilers or shared memory multiprocessors. Lots of very smart people have spent lots of time looking at this. There is no way to make it work without requiring each thread that accesses the LogManager object to perform synchronization

Solution : Leave it as is. The cost of simply making the getInstance() method synchronized is not too high.

An Elegant Solution

Solution 1 : Nice way
import java.io.PrintStream;

public class LogManager {
 public static final LogManager instance = new LogManager(System.out);
 private PrintStream pStream;

 private LogManager(PrintStream out) {
  // To guard against Reflection creating the instance
  if (instance != null) {
   throw new IllegalStateException("instance already exists");
  }
  pStream = out;
 }

 public void log(String msg) {
  pStream.println(msg);
 }
}

Usage: LogManager.instance.log( "some message" );
Positives : much simpler with no performance overhead as compared to above methods as it doesn't uses the synchronized keyword.

Solution 2 : Better way
import java.io.PrintStream;

public class LogManager {
 private static final LogManager instance = new LogManager(System.out);
 private PrintStream pStream;

 private LogManager(PrintStream out) {
  // To guard against Reflection creating the instance
  if (instance != null) {
   throw new IllegalStateException("instance already exists");
  }
  pStream = out;
 }

 public static LogManager getInstance() {
  return instance;
 }

 public void log(String msg) {
  pStream.println(msg);
 }
}

Usage: LogManager.getInstance().log( "some message" );
Positives : In addition to Solution (1) advantages it gives you the flexibility to change your mind about whether the class should be a singleton without changing its API. The factory method returns the sole instance but could easily be modified to return, say, a unique instance for each thread that invokes it.

Serialization caveat :To make a singleton class that is implemented using either of the previous approaches serializable, it is not sufficient merely to add "implements Serializable" to its declaration. To maintain the singleton guarantee, you have to declare all instance fields transient and provide a readResolve method. Otherwise, each time a serialized instance is deserialized, a new instance will be created. To prevent this, add this readResolve method to the Singleton class:

// readResolve method to preserve singleton property
 private Object readResolve() {
  // Return the one true LogManager and let the garbage collector
  // take care of the LogManager impersonator.
  return instance;
 }

Solution 3 : Best way

public enum LogManager {
 INSTANCE;
 private java.io.PrintStream pStream = System.out;

 public void log(String msg) {
  pStream.println(msg);
 }
}

Usage: LogManager.INSTANCE.log( "some message" );
This approach is functionally equivalent to the public field approach, except that it is more concise, provides the serialization machinery for free, and provides an ironclad guarantee against multiple instantiation, even in the face of sophisticated serialization or reflection attacks. While this approach has yet to be widely adopted, a single-element enum type is the best way to implement a singleton.

References: http://c2.com/cgi/wiki?JavaSingleton
References: Effective java book

No comments:

Post a Comment