Saturday, January 21, 2012

Singleton Is Not as Simple as You Think

As Gamma et al.[1] put it, singleton ensures a class only has one instance,and provides a global point of access to it.

Typically, when modelling a system, business objects come out of the nouns defined within a use case statement[2]. These Business Objects are acted upon by the external actors such as User of the System.

If you have only one file system or one window manager in the modelled system, these business objects are good candidates for singletons.

Creating and Destroying Objects

As a Java developer, one design consideration is related to create and destroy objects.  Multiple design decisions need to be made on:
  • When and how to create objects
  • When and how to avoid creating them
  • How to ensure that objects are destroyed in a timely manner
  • How to manage any cleanup actions that must precede object destruction.
Using singleton, it can simplify the above design decisions.  With it, a class can ensure that no other instance can be created (by instercepting requrests to create new objects), and it provides a way to access the instance.

However, as pointed out in this article, using singleton design pattern in a cluster environment is challenging.

Cluster

A computer cluster consists of a set of loosely connected computers that work together.  If all nodes in the system use the same hardware, they are named cluster.  If the nodes use different hardware, they are named grid.  Technologies like cluster or grid have all aimed at allowing access to large amounts of computing power in a fully virtualized manner, by aggregating resources and offering a single system view.  For example, Oracle RAC (Real Application Clusters) allows multiple computers to run Oracle RDBMS software simultaneously while accessing a single database, thus providing a clustered database.

When Fusion web application runs in a clustered environment, it must be possible for the node that the application is running on to fail, but to have it switched to a separate node without any impact on the user. The application server makes this possible by serializing the state that the application requires. If a node goes down, the user is moved to a different node, and the state is automatically recovered.

Similarly managed servers in a Oracle WebLogic domain may be grouped together in a cluster , running simultaneously and working together for increased scalability and reliability[9].  In such a cluster, most resources and services are deployed identically to each managed server, enabling fail-over and load-balancing.  Either active-active and active-passive failover patterns can be supported.  For an active-active configuration, it requires an external load balancer to detect failure and automatically redirect requests.  For an active-passive configuration, it provides a fully redundant instance of each node, which is only brought online when its associated primary node fails.


Multiple Singleton Implementations

Implementation 1

// Singleton with public final field
public class Singleton {
    public static final Singleton INSTANCE = new Singleton();
    private Singleton() { ... }
    public void doSomething() { ... }
}

As noted in [5], a privileged client can invoke the private constructor reflectively with the aid of the AccessibleObject.setAccessible method. If you need to defend against this attack, modify the constructor to make it throw an exception if it’s asked to create a second instance.

Implementation 2

// Singleton with static factory
public class Singleton {
    private static final Singleton INSTANCE = new Singleton();
    private Singleton() { ... }
    public static Singleton getinstance() { return INSTANCE ; }
    public void doSomething() { ... }
}

One advantage of the factory-method approach is that 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.

Implementation 3

// Enum singleton
public enum Singleton {
  INSTANCE;
  public void doSomething() { ... }
}

In [5], Joshua Bloch states that this approach is functionally equivalent to the public field approach (i.e., Implementation 1), 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. In his opinion, a single-element enum type is the best way to implement a singleton.

Implementation 4

// Singleton implemented with lazy initialization
public class Singleton {
 private static Singleton INSTANCE = null;
 protected Singleton() {
   // Exists only to defeat instantiation.
 }
 public synchronized static Singleton getInstance() {
    if(INSTANCE == null) {
       INSTANCE = new Singleton();
    }
    return INSTANCE;
 }
  public void doSomething() { ... }
}

Because synchronization is very expensive performance-wise (synchronized methods can run up to 100 times slower than unsynchronized methods), we can introduce a performance enhancement that only synchronizes the singleton assignment in getInstance()[4].

Implementation 5

// initialization-on-demand holder
public class Singleton {
  private Singleton() { }
  private static class LazyHolder {
    public static final Singleton INSTANCE = new Singleton();
  }
   public static Singleton getInstance() {
     return LazyHolder.INSTANCE;
   }
   public void doSomething() { ... }
}

The static class LazyHolder is only executed when the static method getInstance is invoked on the class Singleton, and the first time this happens the JVM will load and initialize the LazyHolder class. The initialization of the LazyHolder class results in static variable INSTANCE being initialized by executing the (private) constructor for the outer class Singleton.

Since the class initialization phase is guaranteed by the JLS to be serial, i.e., non-concurrent, no further synchronization is required in the static getInstance method during loading and initialization. And since the initialization phase writes the static variable INSTANCE in a serial operation, all subsequent concurrent invocations of the getInstance will return the same correctly initialized INSTANCE without incurring any additional synchronization overhead.

When is a Singleton not a Singleton?

In [3], Joshua Fox has shown that you can inadvertently allow more than one instance to be created under the following circumstances:
  • Multiple singletons in two or more virtual machines
  • Multiple singletons simultaneously loaded by different class loaders
  • Singleton classes destroyed by garbage collection, then reloaded
  • Purposely reloaded singleton classes
  • Multiple instances resulting from incorrect synchronization
  • Multiple singletons arising when someone has subclassed your singletons
  • Multiple singletons created by a factory specially asked to create multiple objects
  • Copies of a singleton object that has undergone serialization and deserialization
  • Multiple singletons caused by problems in a factory
As shown above, implementing singletons in Java is anything but simple. In [4], David Geary tells you how to test singleton classes by using JUnit and log4j.

Singleton in Cluster Environment

To support singletons in a cluster, you need to ask yourselves one question:
  • Is it "single" in the context of the running VM good enough
If your application requires an absolutely single instance in a cluster, you need to read [3] to see how to avoid the pitfalls of creating multiple singletons.

In a cluster environment, application state needs to be serialized and preserved across multiple nodes.  To make a singleton class serializable, it is not sufficient merely to add implements Serializable to its declaration.  To maintain the singleton guarantee, you must also provide a readResolve method.  Otherwise, each deserialization of a serialized instance will result in the creation of a new instance.  To prevent this, add the following readResolve method to the Singleton class:

// readResolve method to preserve singleton property
private Object readResolve() throws ObjectStreamExcetion {
  // Return the one true INSTANCE and let the garbage collector take care of the cloned one
  return INSTANCE;
}

In [7], it has discussed a case that uses singleton to cache some custom information from Database.  This information is mostly read-only but gets refreshed when some particular event occurs.  A question is asked:
  • When this singleton is deployed in a clustered environment, what is the best way to keep the cache in sync.
I'll just refer you to read [7] for possible approaches.  However, in general, the approach is based on the object being cached.
  • If the object being cached is read-only or thread-safe, you simply make the field volatile.
  • If the cache object is mutable and not thread-safe, you have to make sure that any access to mutable fields in the object are synchronized.

References
  1. Design Patterns: Elements of Reusable Object-Oriented Software by Gamma et al.
  2. The Mysteries of Business Object
  3. When is a Singleton not a Singleton?
  4. Simply Singleton
  5. Effective Java by Joshua Bloch
  6. High-availability cluster
  7. Singleton in Cluster environment
  8. Initialization-on-demand holder idiom
  9. Oracle Service Bus Clustering for Developers by Jeff Davies

No comments: