Image source: Canva AI
In certain cases, it’s crucial for specific classes to exist in just a single instance. There are numerous objects where having only one instance is necessary. Creating multiple instances can lead to issues like incorrect program behavior, excessive resource consumption, or unpredictable outcomes. For instance, you typically require only one object of a class when establishing the context of an application, managing a thread pool, or connecting to input or output consoles. Having more than one object of such a type can introduce inconsistencies in your program. The Singleton Pattern guarantees that a class has just one instance and offers a universal means of accessing it. However, despite its simplicity in terms of class structure (with only one class), implementing the Singleton Pattern can be a bit challenging.
In this article, we will explore various ways to create Singleton class, how this Singleton class can break and how to ensure it doesn’t break.
Let’s start with the simplest way possible to create a Singleton class:
class TreeOfTenere {
public static TreeOfTenere instance = new TreeOfTenere();
}
As we know that a class contains only one copy of the static variables, so this class will act as a Singleton as long as the client is using instance static variable. But client can still create a new Object of TreeOfTenere using NoArgsConstructor using new operator.
TreeOfTenere tot = new TreeOfTenere();
So to fix this possiblity, let’s make the constructor private.
class TreeOfTenere {
public static TreeOfTenere instance = new TreeOfTenere();
private TreeOfTenere() {}
}
Now the client will not be able to create another instance of class by directly using the constructor. But providing a direct access to a class property is not a good idea, instead we should expose instance property via method.
class TreeOfTenere {
private static TreeOfTenere instance = new TreeOfTenere();
private TreeOfTenere() {}
public static TreeOfTenere getInstance() {
return instance;
}
}
We have made instance static variable private and exposed it via public static getInstance() method.
If you would have noticed, we are creating an instance of TreeOfTenere while declaring the class static variable. So, there is a possiblility that even if this class instance is never used, JVM will still create an object of this class and keep it in memory. This is not the ideal way of creating objects. Let’s modify our code to create an object, only when it is required.
class TreeOfTenere {
private static TreeOfTenere instance;
private TreeOfTenere() {}
public static TreeOfTenere getInstance() {
if(instance == null){
instance = new TreeOfTenere();
}
return instance;
}
}
In this way, whenever a client tries to access the instance using getInstance() method, method checks if an instance already exists, return that, otherwise create a new instance.
Singleton classes in Multi-threaded environment
Image source: Canva AI
This code might seem good enough for a Singleton class, but it can still fail in mutlithreaded environment. And this can happen when two threads concurrently access TreeOfTenere class. Let’s say Thread t1 calls getInstance() method and checks if instance is null. Then for some reason, sleeps or gets interrupted. Now Thread t2 calls getInstance() method and creates a new object of the class, since instance is null at this time. Later on, Thread t1 resumes execution and creates another instance of the class.
public class SingletonDemo {
public static void main(String[] args) {
Runnable task = ()-> {
System.out.println("Instance hash code : " + TreeOfTenere.getInstance());
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
}
}
Possible Output:
Instance hash code : com.sranmanpreet.designpatterns.TreeOfTenere@5238f79d
Instance hash code : com.sranmanpreet.designpatterns.TreeOfTenere@4e08f976
This can be easily handled using synchronization. Synchronization ensures that not more than one thread accesses the target code block at the same time. There are couple of ways to synchornize a block of code, like synchronization at method level, double-checked locking etc.
java.concurrent API’s provide more advanced ways to synchronization like Semaphores, Count Down Latch, Cyclic Barrier, Locks etc.
Synchronization at Method level
We can synchornize a method by adding synchronized keyword in method signature.
class TreeOfTenere {
private static TreeOfTenere instance;
public synchronized static TreeOfTenere getInstance(){
if(instance == null){
instance = new TreeOfTenere();
}
return instance;
}
}
Synchornization has an impact on the program performance. So it should be used judiciously, only when it is required and it should be applied only to the specific code which needs the synchronization. In the above example, we have synchornized the whole method, but we just need to synchronize the statement where a new object is created. This is where double-checked locking comes handy.
Synchronization using Double-checked lock
class TreeOfTenere {
private static TreeOfTenere instance;
public static TreeOfTenere getInstance(){
if(instance == null){ // first check
synchronized (TreeOfTenere.class){
if(instance == null){ // second check
instance = new TreeOfTenere();
}
}
}
return instance;
}
}
Using double-checked locking, we first verify the existence of an instance, and if it doesn’t exist, we subsequently synchronize. This approach ensures that synchronization only occurs on the first occurrence.
There are couple of more ways in which Singleton pattern can break. We will go through many such ways and try to understand how these can break the Singleton pattern, and how to handle such scenarios.
- Cloneable
- Serialization
- Reflection
Breaking Single pattern using Cloneable
The Cloneable
interface in Java is a marker interface (an interface with no methods) that indicates that the instances of a class can be cloned using the clone()
method. When a class implements the Cloneable
interface, it signifies that objects of that class can be duplicated or cloned to create a new object with the same state.
class TreeOfTenereExtended extends TreeOfTenere implements Cloneable{
public Object clone() throws CloneNotSupportedException{
return super.clone();
}
}
public class SingletonDemo {
public static void main(String[] args) throws Exception {
TreeOfTenereExtended tote = new TreeOfTenereExtended();
TreeOfTenere instance1 = (TreeOfTenere) tote.clone();
TreeOfTenere instance2 = (TreeOfTenere) tote.clone();
System.out.println(instance1);
System.out.println(instance2);
}
}
Output:
com.sranmanpreet.designpatterns.TreeOfTenereExtended@4c203ea1
com.sranmanpreet.designpatterns.TreeOfTenereExtended@27f674d
In above example, we extended our singleton class and implmented Cloneable interface. clone() method of the Cloneable interface is used to create two instances of the TreeOfTenere class.
Preventing Singleton to break from Cloneable
So to ensure that no other class extend your Singleton and implement Cloneable to clone its object, we can implement clone() method in our class.
class TreeOfTenere {
private static TreeOfTenere instance;
public static TreeOfTenere getInstance(){
if(instance == null){
synchronized (TreeOfTenere.class){
if(instance == null){
instance = new TreeOfTenere();
}
}
}
return instance;
}
public Object clone() throws CloneNotSupportedException{
throw new CloneNotSupportedException("TreeOfTenere cannot be clonned");
}
}
Above implementation of clone() method ensures that an exception is thrown whenever a client tries to create a clone of TreeOfTenere class.
Break Singleton pattern using Serialization/Deserialization
If a class is Serializable, we can create create a new instance of the class on deserialization. Here is how:
class TreeOfTenere implements Serializable { private static final long serialVersionUID = -109335841125412L; private static TreeOfTenere instance; public static TreeOfTenere getInstance(){ if(instance == null){ synchronized (TreeOfTenere.class){ if(instance == null){ instance = new TreeOfTenere(); } } } return instance; } } public class SingletonDemo { public static void main(String[] args) throws Exception { try (ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("treeoftenere.txt"))) { TreeOfTenere tot = TreeOfTenere.getInstance(); outputStream.writeObject(tot); } catch (IOException e) { e.printStackTrace(); } // Deserialize the TreeOfTenere from the file try (ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("treeoftenere.txt"))) { TreeOfTenere deserializedTot = (TreeOfTenere) inputStream.readObject(); // At this point, a new instance is created, breaking the Singleton pattern System.out.println("Original Singleton: " + TreeOfTenere.getInstance().hashCode()); System.out.println("Deserialized Singleton: " + deserializedTot.hashCode()); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } } }
Output:
Original Singleton: 1449621165
Deserialized Singleton: 1121172875
As you can see, we are able to create two instances of the TreeOfTenere class. Let’s see how can we handle it.
Preventing Serializable Singleton to break from Deserialization
Override readResolve() and writeReplace() methods to ensure that no duplicate instance of the Singleton is created. These methods are used in conjunction with object serialization to control the behavior of object serialization and deserialization. These methods allow you to customize how objects are serialized and deserialized, including the ability to replace or resolve objects during these processes.
class TreeOfTenere implements Serializable {
private static final long serialVersionUID = -109335841125412L;
private static TreeOfTenere instance;
public static TreeOfTenere getInstance(){
if(instance == null){
synchronized (TreeOfTenere.class){
if(instance == null){
instance = new TreeOfTenere();
}
}
}
return instance;
}
private Object readResolve() throws ObjectStreamException {
return instance;
}
private Object writeReplace() throws ObjectStreamException{
return instance;
}
}
Break Singleton pattern using Reflection API
Reflection API in Java is a set of classes and methods that allows you to examine and manipulate the structure, behavior, and metadata of classes, objects, methods, fields, and other program entities at runtime.
public class SingletonDemo {
public static void main(String[] args) throws Exception {
Constructor constructor = Class.forName("org.sranmanpreet.designpatterns.TreeOfTenere").getDeclaredConstructor();
constructor.setAccessible(true);
TreeOfTenere instance1 = (TreeOfTenere) constructor.newInstance();
TreeOfTenere instance2 = (TreeOfTenere) constructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
Output:
com.sranmanpreet.designpatterns.TreeOfTenere@7c75222b
com.sranmanpreet.designpatterns.TreeOfTenere@4c203ea1
Above code fetches class constructor and makes it accessible. This constructor is then used to create two instances of the TreeOfTenere class, which otherwise was a Singleton class.
Preventing Singleton to break from Reflection API
enum TreeOfTenere{
INSTANCE;
}
Just create an Enum for the Singleton. This will ensure that no two instances are avaiable since enum constants are implicitly static and final and we cannot change their values once created.
Summary
Why Singleton Matters:
- Sometimes, you need a class to have only one instance.
- Creating multiple instances can lead to problems like incorrect behavior or resource waste.
Simplest Singleton:
- Create a static instance variable.
- But clients can still create new instances using the
new
operator.
Making it Private:
- Make the constructor private to prevent direct instantiation.
Exposing Singleton:
- Create a public static
getInstance
method to access the Singleton instance.
Lazy Initialization:
- Only create the instance when needed to optimize resource usage.
Handling Multithreading:
- Without synchronization, multiple threads can create multiple instances.
- Use synchronization to prevent this.
Synchronization Methods:
- You can synchronize the whole method, but it affects performance.
- Double-checked locking helps synchronize only when necessary.
Handling Cloning:
- Implement the
Cloneable
interface, and cloning can create multiple instances. - To prevent this, override the
clone
method to throw an exception.
Serialization Challenges:
- Serialization can create new instances on deserialization, breaking the Singleton.
- To prevent this, use
readResolve
andwriteReplace
methods to control serialization.
Reflection and Singleton:
- Reflection can access private constructors, allowing multiple instances.
- Using an enum for Singleton prevents this, as enum constants are inherently singletons.
So, now you know how to create and protect Singleton instances in Java!