We will start with what Singleton design pattern is and various ways to create a singleton instance of a class e.g lazy initialization, reflection and serialization safe Singleton.
Singleton is a design pattern which comes under the category creational pattern
. Sometimes, it is important to have one instance of class or we can say, it provide single global entry point to the object.
There are many ways to create Singleton. Each one of them have their pros and cons.
In this approach, we eagerly creates an instance of Singleton object during the class load time.
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
// Private constructor
private Singleton() {}
public static final getInstance() {
return INSTANCE;
}
}
In this approach, we lazily creates an instance of Singleton during getInstance
method call. We have to either synchorized whole method or take a mutex
lock in getInstance
method which is also called double-check locking to make it thread safe.
public class Singleton {
private static Singleton INSTANCE = null;
private static final Object MUTEX = new Object();
// Private constructor
private Singleton() {}
public static final getInstance() {
if (INSTANCE == null) {
synchornized(MUTEX) {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
The problem with double-check locking is that it is broken.
There is an article explains why it is broken.
The last solution is taken from the Effective Java book (Item 3) by Joshua Block and uses an enum
instead of a class. At the time of writing, this is considered to be the most concise and safe way to write a singleton.
Eager and Lazy initialzation based approaches explained above are prone to reflection and serialization. To make it safer, we can follow below approach.
public class Singleton {
private static class SingletonInstanceHolder {
private static int MODCOUNT = 0;
private static Singleton INSTANCE = null;
private static final Object MUTEX = new Object();
static {
if (INSTANCE == null) {
synchronized (MUTEX) {
if (INSTANCE == null)
try {
INSTANCE = new Singleton();
} catch (IllegalAccessException e) {
// This is just to show as example
// Don't follow it for production code
e.printStackTrace();
}
}
}
}
}
private Singleton() throws IllegalAccessException {
if(SingletonInstanceHolder.MODCOUNT > 0) {
throw new IllegalAccessException("Multiple instance");
}
SingletonInstanceHolder.MODCOUNT++;
}
public static final Singleton getInstance() {
return SingletonInstanceHolder.INSTANCE;
}
// Avoiding duplicate objects with serialization
public Object readResolve() {
return getInstance();
}
}
We have defined readResolve
method to use getInstance
method to resolve it to correct singleton instance during Serialization/ Deserialization. In Singleton
constructor, we are checking the MODCOUNT
, if it is more than 1
, we are throwing exception and preventing creation of another object using reflection (making constructor public).
The below test code and the exception trace just explains the above to reasoning.
public class SingletonTest {
public static void main(String[] args) throws ClassNotFoundException, SecurityException, NoSuchMethodException, InstantiationException, IllegalAccessException, IllegalArgumentException,InvocationTargetException {
Class demo = Class.forName("singleton.Singleton");
Constructor con = demo.getDeclaredConstructor();
con.setAccessible(true);
for(int i=0; i<10; i++) {
Singleton s1 = (Singleton) con.newInstance();
System.out.println(s1.toString());
}
}
}
The above code when run will throw following InvocationException
. It shows that the Singleton class's object will be created only once and then it will throw exception if tried to called constructor multiple times even with reflection.
Exception in thread "main" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:525)
at singleton.SingletonTest.main(SingletonTest.java:15)
Caused by: java.lang.IllegalAccessException: Multiple instance
at singleton.Singleton.<init>(Singleton.java:29)
... 5 more