Singleton is one of the most basic and easy to use design pattern from the Gang of Four book of design patterns. The GOF describe the singleton pattern as "Ensure a class has only one instance, and provide a global point of access to it". Singleton pattern requires to ensure that only one instance of a class is created in the Java Virtual Machine.
public class Singleton { // Static member holds only one instance of the Singleton class private static Singleton instance; // Singleton prevents any other class from instantiating private Singleton() { } // Providing Global point of access public static Singleton getInstance() { if (null == instance) { instance = new Singleton(); } return instance; } }
Singleton pattern is generally implemented using static factory method called getInstance, static variable holding a class instance and a private constructor. This ensures that singleton instance is created only when required also known as lazy instantiation. The private constructor inhibits sub-classing of the singleton class which if intentional should be made final class or added into an isolated singleton package.
Mutliple classloaders still will be able to have multiple singleton instances as the classes are loaded by different classloaders access a singleton. Hence in multiple JVM environment, each of them will have their own copy of the singleton object which can lead to multiple issues specially in a clustered environment where the access to the resource needs to be restricted and synchronized. The basic method to handle such case is to load the classloader which in turn loads the singleton instance as below.
private static Class getClass(String classname) throws ClassNotFoundException { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); if(classLoader == null) classLoader = Singleton.class.getClassLoader(); return (classLoader.loadClass(classname)); } }
There are multiple other techniques such as JMS, DB, Custom API and 3rd party tools which handle such scenario but they also impact the business logic. Tools like Terracotta and Oracle Coherence work on the concept of providing an in memory replication of objects across JVMs in effect providing a singleton view or making use of any of the cluster-aware cache provider’s like SwarmCache or JBoss TreeCache. Application servers provide some level of custom API’s to circumvent the problem such as JBoss HASingleton Service, Weblogic's concept of Singleton Service and WebSphere's singleton across cluster in the WebSphere XD version.
Also if the singleton implements java.io.Serializable and serialized the object then the subsequent deserialize calls over multiple times would end up with multiple instances of singleton class. The best way to handle this scenario is to make the singleton as an enum enabling the underlying java implementation to handle the details. Other work around would be to implement the readResolve method (or readobject() method) in Singleton class to return the same singleton instance. The clone method is overridden to throw an exception preventing multiple instances of singleton class using cloning.
Also the getInstance method is not thread safe since multiple threads can call getInstance method in parallel creating multiple class instances. The multithreading access can be handled by declaring the getInstance method as synchronized. But synchronized method in turn adds an extra overhead for every call to the getInstance method since synchronized methods can run up to 100 times slower than unsynchronized methods. This can be avoided using the double checked locking as below which checks for synchronization for the very first call to getInstance method when the class instance is not initialized. The double null check is required in the case were a thread can be preempted after entering the synchronized block, before it can assign the singleton member variable, with the subsequent waiting thread to enter the synchronized block. Also when multiple threads tries to read the singleton instance, there is a possibility that a stale or partially initialized value is read by a thread, causing it to create a new instance. The volatile variable from java 5 guarantees that all the writes will happen on volatile variable before any reads, hence avoiding creation of another instance due to race condition.
Unfortunately the double-checked locking is not guaranteed to work because the compiler is free to assign a value to the singleton member variable before the singleton's constructor is called.
public class Singleton { // Singleton instance should we volatile to avoid reading stale value private static volatile Singleton instance; // Singleton prevents any other class from instantiating private Singleton() { } public static Singleton getInstance() { if (null == instance) { synchronized (Singleton.class){ if (null == instance) { instance = new Singleton(); } } } return instance; } }
To provide a fast, easy and thread safe singleton solution, we initialize the class instance by static variables which guaranteed that they are executed only once during the first time when they are accessed. The Classloader guarantees that singleton instance will not be visible until its fully created. The below implementation however compromises the fact that singleton instance can't be changed later to allow multiple instances of Singleton class. The below implementation specifies singleton during compile time as opposed to runtime, a singleton registry implementation can be used which maintains the static HashMap containing the class name as key and the class instance (instantiated using reflection) as the value.
// Singleton Class public class Singleton { // declare a static instance of Singleton class to initialize only once during loading. private static final Singleton instance = new Singleton(); // declare the constructor as private private Singleton() {} // get the instance of singleton class public static Singleton getInstance() { return instance; } }
Another approach to implement singleton which is considered much easier is using enums. Enum guarantees thread-safety by default hence there is no need for double checked locking during instance creation of singleton class. Another problem with conventional implementation of singleton is that once we implement Serializable interface they no longer remain Singleton because readObject() method always returns a new instance just like constructor. This can be avoided by overriding readResolve() method to return the singleton instance and discarding the newly created instance. This can become even more complex if the Singleton class maintained state, were the instance becomes transient. With the Enum approach, as the since enum instances are by default final, it provides safety against multiple instances due to serialization.
// Implement a singleton using enum which is accessed as Singleton.INSTANCE public enum Singleton{ INSTANCE; }
Some of the criticism towards singleton is that they increase the dependencies by adding global code. They make the code tightly coupled, violate single responsibility principle and also maintain their state for the lifetime of the application which complicates unit testing (by initializing singletons in specific order for unit testing). On the other hand singletons are used extensively to ensure that there are no duplicate instances of the class thus avoiding OutOfMemory issues. Hence the default scope for spring beans is singleton since any stateless object may be singleton without causing any concurrency issues.
The most commonly accepted usage of Singletons were they yield the best results is in a situation where various parts of an application concurrently try to access a shared resource such as a Logger.
No comments:
Post a Comment