Singleton Pattern
The Singleton Pattern is one of the simplest design patterns in Java. This type of design pattern falls under the creational pattern category, providing one of the best ways to create an object.
This pattern involves a single class which is responsible for creating its own objects while ensuring that only a single object gets created. This class provides a way to access its only object which can be accessed directly without need to instantiate the object of the class.
Note:
- A singleton class can only have one instance.
- The singleton class must create its own unique instance.
- The singleton class must provide this instance to all other objects.
Introduction
Intent: Ensure a class has only one instance and provide a global point of access to it.
Main Problem: A globally used class is frequently created and destroyed.
When to Use: When you want to control the number of instances and save system resources.
How to Solve: Check if the system already has this singleton; if yes, return it; if not, create it.
Key Code: The constructor is private.
Application Examples:
- A class has only one head teacher.
- Windows is multiprocess and multithreaded. When operating on a file, multiple processes or threads may simultaneously operate on a file, so all file operations must be handled by a single instance.
- Some device managers are often designed as singleton patterns, such as a computer with two printers, where output must ensure that two printers do not print the same file.
Advantages:
- Only one instance in memory, reducing the memory overhead, especially for frequent creation and destruction of instances (like caching the college homepage).
- Avoid multiple occupancy of resources (like file writing operations).
Disadvantages: No interface, cannot be inherited, conflicts with the single responsibility principle. A class should only care about internal logic, not how it is instantiated externally.
Usage Scenarios:
- Requires the production of unique serial numbers.
- Counter in a web application, not adding every refresh to the database, but caching it with a singleton.
- Creating an object requires excessive resources, such as I/O and database connections.
Caution: The getInstance() method needs to use synchronized (Singleton.class) to prevent multiple threads from entering simultaneously and causing multiple instantiations of instance.
Implementation
We will create a SingleObject class. The SingleObject class has its private constructor and a static instance of itself.
The SingleObject class provides a static method for external access to its static instance. The SingletonPatternDemo class uses the SingleObject class to get the SingleObject object.
Step 1
Create a Singleton class.
SingleObject.java
public class SingleObject {
// Create an object of SingleObject
private static SingleObject instance = new SingleObject();
// Make the constructor private so that this class cannot be instantiated
private SingleObject(){}
// Get the only object available
public static SingleObject getInstance(){
return instance;
}
public void showMessage(){
System.out.println("Hello World!");
}
}
Step 2
Get the unique object from the singleton class.
SingletonPatternDemo.java
public class SingletonPatternDemo {
public static void main(String[] args) {
// Illegal constructor
// Compile Time Error: The constructor SingleObject() is not visible
// SingleObject object = new SingleObject();
// Get the only object available
SingleObject object = SingleObject.getInstance();
// Show the message
object.showMessage();
}
}
Step 3
Execute the program, output result:
Hello World!
Several Ways to Implement Singleton Pattern
There are multiple ways to implement the Singleton pattern, as shown below:
1. Lazy Initialization, Thread-Unsafe
Is Lazy Initialization: Yes
Is Thread-Safe: No
Implementation Difficulty: Easy
Description: This is the most basic implementation. The biggest problem with this approach is that it does not support multithreading. It is not technically a Singleton pattern because it lacks synchronized locking.
Example
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
The following implementations support multithreading but have different performance characteristics.
2. Lazy Initialization, Thread-Safe
Is Lazy Initialization: Yes
Is Thread-Safe: Yes
Implementation Difficulty: Easy
Description: This approach has good lazy loading and works well in multithreading scenarios, but it is inefficient, as synchronization is rarely needed in 99% of cases.
Example
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
3. Eager Initialization
Is Lazy Initialization: No
Is Thread-Safe: Yes
Implementation Difficulty: Easy
Description: This approach is commonly used but can create garbage objects.
Example
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
4. Double-Checked Locking (DCL)
JDK Version: JDK1.5 and above
Is Lazy Initialization: Yes
Is Thread-Safe: Yes
Implementation Difficulty: Complex
Description: This approach uses a double-lock mechanism, which is safe and maintains high performance in multithreaded scenarios.
Example
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
5. Static Inner Class
Is Lazy Initialization: Yes
Is Thread-Safe: Yes
Implementation Difficulty: Moderate
Description: This approach achieves the same effect as the double-checked locking method but is simpler to implement. It should be used for static fields, while the double-checked locking method can be used for instance fields requiring lazy initialization.
Example
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
6. Enum
JDK Version: JDK1.5 and above
Is Lazy Initialization: No
Is Thread-Safe: Yes
Implementation Difficulty: Easy
Description: This implementation method is not widely adopted yet, but it is the best way to implement the Singleton pattern. It is more concise, automatically supports serialization mechanisms, and absolutely prevents multiple instantiations.
Example
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
Experience: Generally, the first and second lazy initialization methods are not recommended. The third eager initialization method is recommended. The fifth static inner class method should be used when explicit lazy loading is required. The sixth enum method can be tried when object deserialization is involved. The fourth double-checked locking method can be considered for other special needs.