Singleton Design Pattern: One of a Kind Objects
In software development, sometimes we want to have only one instance of a class. In this case, Singleton Design Pattern should come to mind. Singleton Pattern aims to have only one instance of a class and to provide a global access point to that instance. In this article, we will talk in detail about the Singleton Design Pattern in the Java programming language and show how it is implemented.
Problem:
At most, one instance of a class must be created in an application.
Solution:
That class (singleton) is defined, including its own instance, and the constructor must be private.
What is Singleton Design Pattern?
The singleton design pattern is a creational design pattern that guarantees only one instance of a class and provides a global access point to that instance. This design pattern ensures that an instance of the class is created only once and can be accessed anywhere. In this way, it is aimed to share a single object and ensure consistency.
Ensure that a class has only one instance and provide a global point of access to it.
How to Implement Singleton Design Pattern?
There are basically three steps for a class to conform to the singleton design pattern.
- The constructor must be “private”.
- A “static” member of the same type as the Class is created.
- A “public static method” must be created to access the static member mentioned above. This method is usually named getInstance().
We can implement Singleton Design Pattern in several ways.
1. Lazy Initialization (non-thread-safe)
public class SingletonExample {
// static member of the same type as the class.
private static SingletonExampleinstance;
private SingletonExample() {
// A private constructor method is added to prevent instance creation.
}
//public static method for access to static member.
public static SingletonExample getInstance() {
if (instance == null) {
instance = new SingletonExample();
}
return instance;
}
}
In this implementation, If we do a multi-thread operation, the problem may occur. So how ?
We have two threads, each executing this code. In getInstance() method, there is a case in which two threads might get ahold of different SingletonExample object(instance).
In the code snippet below, when two threads check the instance is null or not null, two thread may decide instance is null at the same time. In this case, instance can be created more than one.
if (instance == null) {
instance = new SingletonExample();
}
2. Lazy Initialization (thread-safe)
By adding the “synchronized” keyword to getInstance(), we force every thread to wait its turn before it can enter the method. That is, no two threads may enter the method at the same time.
public class SingletonExample {
private static SingletonExample instance;
private SingletonExample() {
}
public static synchronized SingletonExample getInstance() {
if (instance == null) {
instance = new SingletonExample();
}
return instance;
}
}
This fixes the problem. However, synchronization is expensive; is this an issue? YES.
3. Non-lazy Initialization (thread-safe)
If your application always creates and uses an instance of Singleton, or if the overhead of Singleton’s creating and runtime aspects isn’t troublesome, you may want to eagerly create your Singleton like this:
public class SingletonExample {
private static SingletonExample instance=new SingletonExample();
private SingletonExample() {
}
public static SingletonExample getInstance(){
return instance;
}
}
In this approach, we rely on the JVM to create the unique instance of the Singleton when the class is loaded. The JVM guarantees that the instance will be created before any thread accesses the static unique Instance variable.
4. Double-check Locking
With double-checked locking, we first check to see if an instance is created, and if not, then we synchronize. This way, we only synchronize the first time through, just what we want.
public class SingletonExample {
private static volatile SingletonExample instance;
private SingletonExample() {
}
public static SingletonExample getInstance() {
if (instance == null) {
synchronized (SingletonExample.class) {
if (instance == null) {
instance = new SingletonExample();
}
}
}
return instance;
}
}
Check for an instance and if not created, enter a synchronized block. In this way, we only synchronize the first time through! After entering the block, check again and if still null, create an instance.
The volatile keyword ensures that multiple threads correctly handle the uniqueInstance variable when initializing a Singleton.