Different ways to make Thread Safe Singleton
- Eager Initialization
- JVM Lazy load - (Best object oriented way)
- Enum
- Double Check locking
Eager Initialization : Example Java 16 Runtime
The best examples of the Eager Initialization Singleton design pattern used by Java 16 is Runtime, below is the source code taken from the JDK directly.
public class Runtime {
private static final Runtime currentRuntime = new Runtime();
private static Version version;
/**
* Returns the runtime object associated with the current Java application.
* Most of the methods of class {@code Runtime} are instance
* methods and must be invoked with respect to the current runtime object.
*
* @return the {@code Runtime} object associated with the current
* Java application.
*/
public static Runtime getRuntime() {
return currentRuntime;
}
/** Don't let anyone else instantiate this class */
private Runtime() {}
...
...
JVM Lazy load Approach:
In this method,J VM handles the creation and invocation , it will load static data members only when required. Thus, this solution is thread-safe and doesn’t require any synchronization as synchronization part is taken care by the JVM.
It is the most efficient & simplistic approach among all the singleton design pattern implementations.
public class Singleton implements Serializable, Cloneable
{
/**
*
*/
private static final long serialVersionUID = 4151350328095030118L;
private static class LazyHolder
{
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance()
{
return LazyHolder.INSTANCE;
}
@Override
protected Object clone() throws CloneNotSupportedException
{
return getInstance();
}
// implement readResolve method to return the existing instance
protected Object readResolve()
{
return getInstance();
}
}
Enum Approach
An Enum is singleton by design. All the enum values are initialized by JVM only once i.e at the time of class loading. Creation and Invocation of enum constructors are taken care by JVM internally, thus they are thread safe
public enum SingletonEnum
{
INSTANCE;
int value;
public int getValue()
{
return value;
}
public void setValue(int value)
{
this.value = value;
}
}
Double Check Locking
In this approach we double-check whether the variable is initialized or not inside the synchronized block.
public class SingletonDCL
{
private static volatile SingletonDCL singletonDCL = null;
private SingletonDCL()
{
throw new IllegalArgumentException("Initialization through Reflection not allowed");
}
public static SingletonDCL getInstance()
{
if (singletonDCL != null)
return singletonDCL;
synchronized (SingletonDCL.class)
{
if (singletonDCL == null)
singletonDCL = new SingletonDCL();
}
return singletonDCL;
}
}
This can be explained as Let’s say that two threads T1 and T2 enter the getInstance() method simultaneously. The instance==null check will evaluate to true, so both of them will enter the synchronized block one-by-one. If the double check was not there, both threads would create a new instance.
Also, notice the use of volatile keyword with the instance variable. This is necessary to prevent compiler optimizations (i.e they reorder instructions). Wikipedia has a great explanation of double-checked locking along with Java code. Check that out here.
Interview Question : Can Singleton be Serializable/Cloneable ?
Answer is Yes for both. Singletons can be Serializable & Cloneable both, It requires extra care to make sure that the behavior is kept intact though. Above examples and below explanation throws some light on this.How to prevent Singleton from Reflection, Serialization & Cloning ?
Protection from Reflection:
Simplest is to throw exception from the private constructor, Check above Double Check Locking example.Another Solution is usage of Enums, as they can't be initialized via Reflection.
Protection from SerializationreadResolve method is called when ObjectInputStream has read an object from the stream and is preparing to return it to the caller.ObjectInputStream checks whether the class of the object defines the readResolve method. If the method is defined, the readResolve method is called to allow the object in the stream to designate the object to be returned.So if we return the existing instance the Singleton behavior is maintained . Check above examples.Protection from Cloning
This can be resolved via two ways
- Throw exception from clone method , this way cloning of singletons are not allowed
- Return the same instance (as shown in above example)
What’s next? Subscribe Learn INQuiZitively to be the first to read my stories.