Design Patterns - Singleton (in Java)
The ultimate guide on singleton design pattern
Definition
A class should have only one instance and that should be accessible globally.
This is a Creational Design Pattern.
Idea Behind Singleton Design Pattern
Limit object creation by making the constructor private
Expose a public static method to get the instance of that class
Implementation
There are multiple ways to implement singleton design patterns.
Eager Initialization
The main idea is to create an object and make it final. Always return that single object from the static method.
public class Singleton {
private static final Singleton instance = new Singleton(); // egar initialization
private Singleton() { // private constructor to prohibit object creation
}
public static Singleton getInstance() { // public static method to expose already created single instance
return instance;
}
}
Lazy Initialization
The idea is to create a single instance only when a client requests it and always return the same instance each time the client asks. This is a memory-optimized implementation.
public class Singleton {
private static Singleton instance;
private Singleton() { // private constructor to prohibit object creation
}
public static Singleton getInstance() { // public static method to expose single instance
if (instance == null) { // lazy initialization:
// create single instance when client ask for it for the first time
instance = new Singleton();
}
return instance;
}
}
Limitations
This implementation is correct as long as you are thinking in a single-threaded way.
There is a high chance of calling getInstance()
from multiple threads create multiple instances of the Singleton
class. So this implementation is not thread-safe.
Thread-Safe Singleton
Use synchronized
keyword on getInstance()
method to ensure thread safety.
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
Or we can use synchronized
block inside getInstance()
method.
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
return instance;
}
}
But the use of synchronized
is a costly operation as it involves locking and unlocking the code chunk.
So a tricky solution could be use synchronized
conditionally.
Double-Checked Locking
public class Singleton {
private static volatile Singleton instance; // use volatile to ensure visibility
// and solve instruction re-ordering problem
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) { // first check
synchronized (Singleton.class) {
if (instance == null) { // second check
instance = new Singleton();
}
}
}
return instance;
}
}
Bill Pugh Singleton Design
This is named after the inventor of this implementation Bill Pugh.
public class Singleton {
private Singleton() {
}
private static class SingletonHelper {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHelper.INSTANCE;
}
}
This nice tricky implementation is also known as the Initialization-on-demand holder idiom.
This ensures lazy initialization as well as thread safety. It is less complex to implement. As there is no synchronization and locking - it reduces performance overhead.
Importance of Singleton Design Pattern
For some resources e.g. database connection, logging, configurations, etc. we want only one instance so that it reduces duplication, and ensures correctness.
In some cases, object creation may be costly and unnecessary, in those cases, singleton is the escape.
There might be a situation when you need control over instances of a class, singleton is very useful in that scenario.
Some practical examples of singleton are - transaction management, global thread pool manager, etc.