Item 3: Enforce the singleton property with a private constructor or an enum type - saurabhojha/Effective-java GitHub Wiki
Singleton: In mathematics, a singleton, also known as a unit set, is a set with exactly one element.
Singleton design pattern, restricts the no of objects instantiated for the class to one. There are various ways to implement a singleton class.
Three important things to consider while writing a singleton class:
- A singleton class should be thread safe. (It is not singleton if multiple threads are able to instantiate objects for our singleton.)
- A singleton class should be serialisation safe. (You should not have 2 different objects after serialisation and deserialisation.)
- A singleton class should be reflection proof. (You should not be able to instantiate more than one object for the class by overriding the access privileges using the java reflection api.)
1. Eager Initialisation:
In eager initialisation, we use a static instance of the class. Since it is static instance, it is created at the class loading time. This ensures that we always have a single instance of the object available with us(one instantiation per class loader). Such an instantiation is thread safe.
Following code depicts an eager initialised version of singleton class:
class Singleton {
public static final Singleton instance = new Singleton();
private Singleton() {
}
}
The advantage of such a singleton is thread safe since the "instance" field is static.
However, the disadvantages of such a class are immense.
- We initialise the instance "eagerly" even though we might not use it at all. This can cause issues when the object instantiation for the singleton class is expensive.
- This implementation of singleton class is not reflection safe. You can use the java reflection api to invoke the private constructor!!
The following code can create two instances.
import java.lang.reflect.InvocationTargetException;
class Singleton {
public static final Singleton instance = new Singleton();
private Singleton() {
}
}
public class Main {
public static void main(String[] args)
{
Singleton s1 = Singleton.instance;
Singleton s2 = null;
try
{
Class<Singleton> classSingleton = Singleton.class;
java.lang.reflect.Constructor<Singleton> constructor = classSingleton.getDeclaredConstructor();
constructor.setAccessible(true);
s2 = constructor.newInstance();
} catch (InvocationTargetException | InstantiationException | IllegalAccessException | NoSuchMethodException e) {
e.printStackTrace();
}
if(s1.hashCode() == s2.hashCode()) {
System.out.println("s1 is same as s2");
}
else {
System.out.println("s1 is different from s2");
}
}
}
The code generates the following output:
s1 is different from s2
Process finished with exit code 0
2. Lazy loading:
This form of singleton class is instantiated once using a static factory method, only when used. This removes the first disadvantage of eager instantiation.
In simplest form, our lazy loaded singleton class can be defined as follows:
class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance()
{
if(instance==null) {
instance = new Singleton();
}
return instance;
}
}
Notice how the instance field is now private and the getInstance() static factory method is being used to return the instance field. This implementation however is not a good implementation.
1. Making lazy loading thread safe:
The implementation above is not thread safe.
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
Singleton s1 = Singleton.getInstance();
System.out.println("Instance 1 hash:" + s1.hashCode());
});
Thread t2 = new Thread(() -> {
Singleton s2 = Singleton.getInstance();
System.out.println("Instance 2 hash:" + s2.hashCode());
});
t1.start();
t2.start();
}
The code above generates the following output:
Instance 2 hash:1666313853
Instance 1 hash:277560248
Process finished with exit code 0
This happens because the getInstance() method is not synchronized and two threads can thus instantiate different objects. One way of ensuring thread safety is by synchronizing the entire getInstance() method. This however might affect the performance. Synchronized codes are slower than non synchronized ones.
class Singleton {
private static Singleton instance;
private Singleton() {
}
public synchronized static Singleton getInstance()
{
if(instance==null) {
instance = new Singleton();
}
return instance;
}
}
One way to overcome this is using the double checked locking.
class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance()
{
if(instance==null) {
synchronized (Singleton.class) {
if(instance==null) //double check inside the synchronized block.
instance = new Singleton();
}
}
return instance;
}
}
The double checked method in theory ensures that the object creation is limited to only one and gives us the advantage of synchronizing only a block of code instead of entire function, thereby improving the performance. However, due to the java memory model, double checked locking is not a safe programming construct and should not be used. Read this and this to understand more.
Hence we will stick with entire getInstance() method synchronized for the entirety of this post.
2. Making lazy loading reflection safe: In order to avoid the constructor being invoked by the reflection api, we can modify it to throw a runtime error. This would make the class reflection proof.
class Singleton {
private static Singleton instance;
private Singleton() {
if(instance!=null)
throw new RuntimeException("Private constructor invoked!!");
}
public synchronized static Singleton getInstance()
{
if(instance==null) {
instance = new Singleton();
}
return instance;
}
}
Now we can verify if our code is reflection proof or not.
import java.lang.reflect.InvocationTargetException;
import java.sql.SQLOutput;
class Singleton {
private static Singleton instance;
private Singleton() {
if(instance!=null)
throw new RuntimeException("Private constructor invoked!!");
}
public synchronized static Singleton getInstance()
{
if(instance==null) {
instance = new Singleton();
}
return instance;
}
}
public class Main {
public static void main(String[] args)
{
Singleton s1 = Singleton.getInstance();
Singleton s2 = null;
try {
Class<Singleton> classSingleton = Singleton.class;
java.lang.reflect.Constructor<Singleton> constructor = classSingleton.getDeclaredConstructor();
constructor.setAccessible(true);
try {
s2 = constructor.newInstance();
} catch (RuntimeException e) {
System.out.println(e.getMessage());
}
} catch (InvocationTargetException | InstantiationException | IllegalAccessException | NoSuchMethodException e) {
e.printStackTrace();
}
}
}
Running this code gives us the following output:
java.lang.reflect.InvocationTargetException
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:64)
at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:500)
at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:481)
at Main.main(Main.java:34)
Caused by: java.lang.RuntimeException: Private constructor invoked!!
at Singleton.<init>(Main.java:10)
... 6 more
3. Making lazy loading serialisation safe:
The singleton class we made is still not serialisation safe. If we serialise and then deserialise the object of our singleton, we end up with two instances. The code below demonstrates the same.
import java.io.*;
class Singleton implements Serializable {
private static Singleton instance;
private Singleton() {
if(instance!=null)
throw new RuntimeException("Private constructor invoked!!");
}
public synchronized static Singleton getInstance()
{
if(instance==null) {
instance = new Singleton();
}
return instance;
}
}
public class Main {
public static void main(String[] args)
{
try {
Singleton s1 = Singleton.getInstance();
//SERIALIZING OUR s1 INSTANCE
ObjectOutput ostream = new ObjectOutputStream(new FileOutputStream("singletonSerial.ser"));
ostream.writeObject(s1);
ostream.close();
//DESERIALIZING OUR s1 INSTANCE INTO s2
ObjectInput istream = new ObjectInputStream(new FileInputStream("singletonSerial.ser"));
Singleton s2 = (Singleton) istream.readObject();
istream.close();
if(s1.hashCode() == s2.hashCode()) {
System.out.println("s1 is same as s2");
}
else {
System.out.println("s1 is different from s2");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
The output for the code is:
s1 is different from s2
Process finished with exit code 0
The standard method to make a singleton serialisable safe is by providing a readResolve method. This method is invoked at the time of object deserialisation making the singleton class instantiate only one object.
class Singleton implements Serializable {
private static Singleton instance;
private Singleton() {
if(instance!=null)
throw new RuntimeException("Private constructor invoked!!");
}
public synchronized static Singleton getInstance()
{
if(instance==null) {
instance = new Singleton();
}
return instance;
}
protected Object readResolve() {
return getInstance();
}
}
Even though we have covered all the three standards that was mentioned earlier, there are still cases where your singleton can initialise two objects or more. This happens when the class is initialised by different class loaders.
Refer to this link to know more about it.
You can read about singleton classes and how one can write tests using JUnit and log4j here.
So far we have seen all the shortcomings of implementing a singleton class. Fortunately, java provides us with enums which are inherently thread safe, reflection safe and serialisation safe. However, the serialisation process of enums is different from classes. A serialised enumeration constant contains only names and the corresponding field values are not present.
//A simple enum singleton
public enum Singleton {
INSTANCE;
}
There are a few disadvantages of this approach.
- Enums are inherently eager initialisation. You cannot lazy load them.
- In case you are unsure about a class remaining singleton in future, enums are not a good choice.
- Enums cannot extend classes. This limits the enum singletons.
This approach is similar to the public field approach, but 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. This approach may feel a bit unnatural, but a single-element enum type is often the best way to implement a singleton.