Design Patterns in Java: Singleton
Learn more about the fives types of the singleton design pattern and best practices.
Join the DZone community and get the full member experience.
Join For FreeA design pattern is defined as the re-usable form of a solution to a design problem. With a design pattern, you can document a solution to a design problem. Design patterns were introduced by the architect Christopher Alexander and have been adapted for various other disciplines and are very popular among developers, as they provide solutions to general problems that software developers face. It provides a template to solve common problems while designing a system or an application.
Design patterns act as a common language between developers so that each developer easily understands the concept while working with common problems.
Design patterns have evolved after many trials and captured solutions that have been applied to problems successfully. Even a new developer can learn a lot from these design patterns.
Benefits of Using Design Patterns in Java
- Design patterns have evolved after successfully being applied to problems. Today, design patterns are already defined and provide a standard approach to solve common problems. It saves a lot of developer’s time if they use these patterns sensibly.
- With design patterns, developers can develop more robust and maintainable code. It also promotes reusability and helps in reducing the total development cost of the application.
- Design patterns are already tested and successfully applied in many common recurring problems, therefore, applying these patterns will help in making code easy to understand, debug, and faster to develop.
In the context of Java, design patterns are divided into three categories – creational, structural, and behavioral patterns.
Creational Design Patterns
With a creational design pattern, you can instantiate an object in the best possible manner for specific problems. The object creation process can lead to unwanted complexity in the design problems. This is where creational design patterns provide a solution by controlling the object creation process in different ways. There are five different creational design patterns:
- Singleton Pattern
- Factory Pattern
- Abstract Factory Pattern
- Builder Pattern
- Prototype Pattern
For this article, we’ll be covering the singleton pattern. The singleton pattern makes sure that only one instance of the class exists by restricting instantiation of a class. It is one of the Gangs of Four Design Patterns. Additionally, it is one of the simplest design patterns in Java. Although it is the simplest design pattern, it has a lot of implementation concerns.
Let’s learn about singleton design pattern principles, its usage, best practices, and different ways to implement.
1. Singleton Eager Initialization
In Eager Initialization, the instance of a class is created
at the time of class loading:
public class SingletonEager {
private static volatile SingletonEager instance = new SingletonEager ();
// private constructor
private SingletonEager() {
}
public static SingletonEager getInstance() {
return instance;
}
}
The instance is created irrespective of whether the instance is required in runtime or not in the client application. To solve this issue, you can leave the instance unused if it’s not a big object.
2. Static Block Initialization
Static blocks are executed during class loading before the constructor is called. It is similar to Eager Initialization except for the fact that the class is created in a static block, which provides an option for exception handling.
public class SingletonStaticBlock {
private static final SingletonStaticBlock INSTANCE;
static {
try {
INSTANCE = new SingletonStaticBlock ();
} catch (Exception e) {
throw new RuntimeException(“It wasn’t the way expected!”, e);
}
}
public static SingletonStaticBlock getInstance() {
return INSTANCE;
}
private SingletonStaticBlock() {
// …
}
}
The drawback of this type of initialization is that if only two or three static fields are required out of a few static fields, we need to create an instance irrespective of whether it is required or not.
3. Lazy Initialization
Lazy initialization creates an instance in the global access method. By lazy initialization, you can actually delay the creation of an object until it is required.
public final class SingletonLazy {
private static volatile SingletonLazy instance = null;
// private constructor
private SingletonLazy() {
}
public static SingletonLazy getInstance() {
if (instance == null) {
synchronized (SingletonLazy.class) {
instance = new SingletonLazy();
}
}
return instance;
}
}
With this method, you can check if the instance is already created using an instance variable. It will create an instance if the instance is null, or else it will return the reference if the instance is already created.
If there are two threads, both will create the instance and check if the instance is null. Now, if both the threads identify a null instance, then they’ll create an instance by sequentially going into a synchronized block. This will result in two instances at the end. To resolve the issue, you can use double-checked locking method. It will recheck the instance variable in a synchronized block.
public class SingletonLazy {
private static volatile SingletonLazy instance = null;
// private constructor
private SingletonLazy() {
}
public static SingletonLazy getInstance() {
if (instance == null) {
synchronized (SingletonLazy.class) {
// Double check
if (instance == null) {
instance = new SingletonLazy();
}
}
}
return instance;
}
}
4. Bill Pugh Singleton
Bill Pugh devised an approach to create Singleton class using inner static helper class. Due to issues with Java memory model, if there are multiple threads, then each thread will create instance simultaneously. This is where Bill Pugh Singleton method works.
public class SingletonByBillPugh {
private SingletonByBillPugh() {
}
private static class LazyHolder {
private static final SingletonByBillPugh INSTANCE = new
SingletonByBillPugh();
}
public static SingletonByBillPugh getInstance() {
return LazyHolder.INSTANCE;
}
}
5. Singleton Using Enum
This Singleton pattern was suggested by Joshua Bloch to use Enum as its value is instantiated only once in Java. With Enum, only one instance is guaranteed. It provides implicit support for thread safety and is a good way to have singleton with minimal effort.
public enum SingletonEnum {
INSTANCE;
public void someMethod(String param) {
// some class member
}
}
6. Singleton Object With readResolve()
If the application is distributed, objects are serialized frequently into the file system so that they can be read later when required. While de-serializing, the objects always creates a new instance.
public class SingletonDemo implements Serializable {
private volatile static SingletonDemo instanceOfSingleton = null;
public static SingletonDemo getInstance() {
if (instanceOfSingleton == null) {
instanceOfSingleton = new SingletonDemo();
}
return instanceOfSingleton;
}
private int i = 10;
public int getI() {
return i;
}
public void setI(int i) {
this.i = i;
}
}
We’ll serialize this class and again de-serialize it after some changes:
public class SerializableTest {
static SingletonDemo firstInstance = SingletonDemo.getInstance();
public static void main(String[] args) {
try {
// Serialize to a file
ObjectOutput out = new ObjectOutputStream(new FileOutputStream(“filename.ser”));
out.writeObject(firstInstance);
out.close();
firstInstance.setI(20);
// Serialize to a file
ObjectInput in = new ObjectInputStream(new FileInputStream(“filename.ser”));
SingletonDemo secondInstance = (SingletonDemo) in.readObject();
in.close();
System.out.println(firstInstance.getI());
System.out.println(secondInstance.getI());
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
Output:
20
Since both variables have different values, there will be two instances in the class that will create the same problem of multiple instances in the application. To resolve the issue, we’ll include the readResolve()
method in our class. The readResolve() method is invoked when the object is de-serialized. The existing instance must be returned inside this method to ensure a single instance.
public class SingletonDemo implements Serializable {
private volatile static SingletonDemo instance = null;
public static SingletonDemo getInstance() {
if (instance == null) {
instance = new SingletonDemo();
}
return instance;
}
protected Object readResolve() {
return instance;
}
private int i = 10;
public int getI() {
return i;
}
public void setI(int i) {
this.i = i;
}
}
Output:
20
20
7. Singleton Object With serialVersionUId
Serial version id is required when the class structure changes between serialization and de-serialization. The Java Virtual Machine will give an exception in de-serializing process when there is a changed class structure.
java.io.InvalidClassException: singleton.SingletonDemo; local class incompatible: stream classdesc serialVersionUID = 5026910492258526905, local class serialVersionUID =3597984220566440782
at java.io.ObjectStreamClass.initNonProxy(Unknown Source)
at java.io.ObjectInputStream.readNonProxyDesc(Unknown Source)
at java.io.ObjectInputStream.readClassDesc(Unknown Source)
at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
at java.io.ObjectInputStream.readObject0(Unknown Source)
at java.io.ObjectInputStream.readObject(Unknown Source)
at singleton.SerializationTest.main(SerializableTest.java:24)
Only by adding a serial version id to the class can this issue can be resolved. The compiler will be prevented from throwing the exception; instead, it will load the available instance variables only. Now, you must have an idea how to implement the singleton design pattern in your application. You can follow the below mentioned code to ensure that only one instance of a class is there in the whole application.
public class SingletonDemo implements Serializable {
private static final long serialVersionUID = 1L;
private SingletonDemo() {
// private constructor
}
private static class SingletonDemoHolder {
public static final SingletonDemo INSTANCE = new SingletonDemo();
}
public static SingletonDemo getInstance() {
return SingletonDemoHolder.INSTANCE;
}
protected Object readResolve() {
return getInstance();
}
}
I hope this article helps you to understand the details of the singleton design pattern and singleton best practices.
Opinions expressed by DZone contributors are their own.
Comments