Prevent Breaking a Singleton Class Pattern
Let's explore the three major ways in which you can break a singleton property — and how to prevent it.
Join the DZone community and get the full member experience.
Join For FreeWe are used to having a singleton design pattern in our applications whenever it is needed. As we know, in the singleton design pattern, we can only create one instance and access it in the whole application. But in some cases, it will break the singleton behavior.
You may also like: All About the Singleton
There are mainly three concepts in which we can break the singleton property of a Singleton
class in Java. In this post, we will discuss how it can break and how to prevent it.
Here is sample Singleton
class and SingletonTest
class.
Singleton.Java
package demo1;
public final class Singleton {
private static volatile Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
SingletonTest.java
package demo1;
public class SingletonTest {
public static void main(String[] args) {
Singleton object1 = Singleton.getInstance();
Singleton object2 = Singleton.getInstance();
System.out.println("Hashcode of Object 1 - " + object1.hashCode());
System.out.println("Hashcode of Object 2 - " + object2.hashCode());
}
}
Here is output; you can see it has the same hashcode for objectOne
and objectTwo:
Hashcode of Object 1 - 1836019240
Hashcode of Object 2 - 1836019240
Now, we will break this pattern. First, we will use Java reflection.
Reflection
Java Reflection is an API used to examine or modify the behavior of methods, classes, and interfaces at runtime. Using the Reflection API, we can create multiple objects in the Singleton
class. Consider the following example:
ReflectionSingleton.java
package demo1;
import java.lang.reflect.Constructor;
public class ReflectionSingleton {
public static void main(String[] args) {
Singleton objOne = Singleton.getInstance();
Singleton objTwo = null;
try {
Constructor constructor = Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
objTwo = (Singleton) constructor.newInstance();
} catch (Exception ex) {
System.out.println(ex);
}
System.out.println("Hashcode of Object 1 - "+objOne.hashCode());
System.out.println("Hashcode of Object 2 - "+objTwo.hashCode());
}
}
This example shows how reflection can break the singleton pattern with Java reflect. You will get two hash codes, as shown below. It has a break on the singleton pattern.
Prevent Singleton Pattern From Reflection
There are many ways to prevent Singleton pattern from Reflection API, but one of the best solutions is to throw a run-time exception in the constructor if the instance already exists. In this, we can not able to create a second instance.
Deserialization
In serialization, we can save the object of a byte stream into a file or send over a network. Suppose if you serialize the Singleton
class, and then again de-serialize that object, it will create a new instance, hence deserialization will break the Singleton pattern.
The below code is used to illustrate how the Singleton
pattern breaks with deserialization.
Implements Serializable interface for Singleton
Class.
DeserializationSingleton.Java
package demo1;
import java.io.*;
public class DeserializationSingleton {
public static void main(String[] args) throws Exception {
Singleton instanceOne = Singleton.getInstance();
ObjectOutput out = new ObjectOutputStream(new FileOutputStream("file.text"));
out.writeObject(instanceOne);
out.close();
ObjectInput in = new ObjectInputStream(new FileInputStream("file.text"));
Singleton instanceTwo = (Singleton) in.readObject();
in.close();
System.out.println("hashCode of instance 1 is - " + instanceOne.hashCode());
System.out.println("hashCode of instance 2 is - " + instanceTwo.hashCode());
}
}
The output is below, and you can see two hashcodes.
hashCode of instance 1 is - 2125039532
hashCode of instance 2 is - 381259350
Prevent Singleton Pattern From Deserialization
To overcome this issue, we need to override readResolve()
method in the Singleton
class and return the same Singleton instance. Update Singleton.java, with below method.
protected Object readResolve() {
return instance;
}
Now, run the above DeserializationDemo
class and see the output.
hashCode of instance 1 is - 2125039532
hashCode of instance 2 is - 2125039532
Cloning
Using the "clone
" method, we can create a copy of the original object; it's the same thing if we applied clone
in the singleton pattern. It will create two instances: one original and another one cloned object. In this case, we will break the Singleton principle, as shown in the below code.
Implement the " Cloneable
" interface and override the clone method in the above Singleton
class.
Singleton.java
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
Then, test with cloning for breaking the singleton.
CloningSingleton.java
public class CloningSingleton {
public static void main(String[] args) throws CloneNotSupportedException, Exception {
Singleton instanceOne = Singleton.getInstance();
Singleton instanceTwo = (Singleton) instanceOne.clone();
System.out.println("hashCode of instance 1 - " + instanceOne.hashCode());
System.out.println("hashCode of instance 2 - " + instanceTwo.hashCode());
}
}
Here is the output:
hashCode of instance 1 - 1836019240
hashCode of instance 2 - 325040804
If we see the above output, two instances have different hashcodes. This means these instances are not the same.
Prevent Singleton Pattern From Cloning
In the above code, it breaks the Singleton principle, i. e created two instances. To overcome the above issue, we need to implement/override the clone()
method and throw an exception CloneNotSupportedException
from the clone
method. If anyone tries to create a clone
object of Singleton
, it will throw an exception, as shown in the below code.
@Override
protected Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
Now, we can run the CloningSingleton
class; it will throw CloneNotSupportedException
while creating a clone object of the Singleton
object.
Hope you found this post useful!
Further Reading
Published at DZone with permission of Madhuka Udantha, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments