A Singleton class in Java is a class that allows only one instance of itself to be created throughout the lifetime of the application.
The Singleton design pattern is useful when you need exactly one object to coordinate actions across the system, such as managing a database connection, logging, or controlling access to shared resources.
In this tutorial, we will explore different ways to implement a Singleton class in Java, with various code examples demonstrating the best practices.
Key Features of a Singleton Class:
Private Constructor: The constructor is made private to prevent direct instantiation from outside the class.
Static Instance: The class holds a static instance of itself.
Global Access: A public static method provides a global point of access to the instance.
1. Eager Initialization Singleton
In eager initialization, the instance of the Singleton class is created at the time of class loading. This is the simplest method to create a Singleton, but it may lead to resource wastage if the instance is never used.
Example:
public class EagerSingleton { // Static instance of the Singleton class, created eagerly private static final EagerSingleton INSTANCE = new EagerSingleton(); // Private constructor to prevent instantiation private EagerSingleton() { System.out.println("EagerSingleton instance created"); } // Public method to provide access to the instance public static EagerSingleton getInstance() { return INSTANCE; } } public class Main { public static void main(String[] args) { // Accessing the Singleton instance EagerSingleton singleton = EagerSingleton.getInstance(); } }
Explanation:
The INSTANCE is created at class loading time.
The getInstance() method provides global access to the Singleton instance.
Output:
EagerSingleton instance created (printed only once).
2. Lazy Initialization Singleton
In lazy initialization, the instance is created only when it is needed, i.e., when getInstance() is called for the first time. This can save resources if the instance is never used.
Example:
public class LazySingleton { // Static instance of the Singleton class, not initialized initially private static LazySingleton instance; // Private constructor to prevent instantiation private LazySingleton() { System.out.println("LazySingleton instance created"); } // Public method to provide access to the instance, creating it lazily public static LazySingleton getInstance() { if (instance == null) { instance = new LazySingleton(); } return instance; } } public class Main { public static void main(String[] args) { // Accessing the Singleton instance LazySingleton singleton = LazySingleton.getInstance(); } }
Explanation:
The instance is created lazily when getInstance() is first called.
The instance is created only if instance == null.
Output:
LazySingleton instance created.
3. Thread-Safe Singleton (Synchronized)
To make the Singleton thread-safe, we can synchronize the getInstance() method so that only one thread can execute it at a time. This prevents race conditions in multithreaded environments.
Example:
public class ThreadSafeSingleton { // Static instance of the Singleton class, not initialized initially private static ThreadSafeSingleton instance; // Private constructor to prevent instantiation private ThreadSafeSingleton() { System.out.println("ThreadSafeSingleton instance created"); } // Synchronized method to ensure thread safety public static synchronized ThreadSafeSingleton getInstance() { if (instance == null) { instance = new ThreadSafeSingleton(); } return instance; } } public class Main { public static void main(String[] args) { // Accessing the Singleton instance ThreadSafeSingleton singleton = ThreadSafeSingleton.getInstance(); } }
Explanation:
The synchronized keyword ensures that only one thread can create the instance.
This prevents multiple threads from creating multiple instances.
Output:
ThreadSafeSingleton instance created.
4. Double-Checked Locking Singleton
The double-checked locking technique reduces the overhead of synchronization by only synchronizing the block of code that creates the instance, rather than the entire method. This is more efficient than synchronizing the entire method.
Example:
public class DoubleCheckedLockingSingleton { // Static instance of the Singleton class, using volatile to ensure visibility across threads private static volatile DoubleCheckedLockingSingleton instance; // Private constructor to prevent instantiation private DoubleCheckedLockingSingleton() { System.out.println("DoubleCheckedLockingSingleton instance created"); } // Double-checked locking for thread-safe and efficient Singleton public static DoubleCheckedLockingSingleton getInstance() { if (instance == null) { synchronized (DoubleCheckedLockingSingleton.class) { if (instance == null) { instance = new DoubleCheckedLockingSingleton(); } } } return instance; } } public class Main { public static void main(String[] args) { // Accessing the Singleton instance DoubleCheckedLockingSingleton singleton = DoubleCheckedLockingSingleton.getInstance(); } }
Explanation:
The volatile keyword ensures that the instance variable is read from the main memory, not from the thread's local cache.
The first if check prevents unnecessary synchronization, and the second if ensures that the instance is only created once.
Output:
DoubleCheckedLockingSingleton instance created.
5. Bill Pugh Singleton (Inner Static Helper Class)
This method is considered one of the best ways to implement a Singleton in Java. It takes advantage of Java's class loading mechanism to ensure that the Singleton is created only when needed, without the need for synchronization.
Example:
public class BillPughSingleton { // Private constructor to prevent instantiation private BillPughSingleton() { System.out.println("BillPughSingleton instance created"); } // Inner static helper class responsible for holding the Singleton instance private static class SingletonHelper { // The Singleton instance is created when this class is loaded private static final BillPughSingleton INSTANCE = new BillPughSingleton(); } // Public method to provide access to the Singleton instance public static BillPughSingleton getInstance() { return SingletonHelper.INSTANCE; } } public class Main { public static void main(String[] args) { // Accessing the Singleton instance BillPughSingleton singleton = BillPughSingleton.getInstance(); } }
Explanation:
The SingletonHelper class is not loaded until getInstance() is called, ensuring lazy initialization.
This approach is thread-safe without requiring synchronization.
Output:
BillPughSingleton instance created.
6. Enum Singleton
Using an enum to implement a Singleton is the most robust way, as it provides thread safety, serialization safety, and guarantees that only one instance will be created.
Example:
public enum EnumSingleton { INSTANCE; // The single instance public void doSomething() { System.out.println("Enum Singleton is working!"); } } public class Main { public static void main(String[] args) { // Accessing the Singleton instance EnumSingleton singleton = EnumSingleton.INSTANCE; singleton.doSomething(); } }
Explanation:
The INSTANCE is the single instance of EnumSingleton.
Enum-based Singleton ensures that only one instance is created, even in multithreaded environments or when dealing with serialization.
Output:
Enum Singleton is working!.
7. Singleton with Reflection Safe
One of the drawbacks of using traditional Singleton methods is that reflection can break the Singleton pattern by allowing multiple instances to be created. To prevent this, you can add a check in the constructor.
Example:
public class ReflectionSafeSingleton { // Static instance of the Singleton class private static ReflectionSafeSingleton instance; // Private constructor to prevent instantiation private ReflectionSafeSingleton() { // Prevent reflection from breaking Singleton if (instance != null) { throw new RuntimeException("Cannot create instance using reflection"); } System.out.println("ReflectionSafeSingleton instance created"); } // Public method to provide access to the instance public static ReflectionSafeSingleton getInstance() { if (instance == null) { instance = new ReflectionSafeSingleton(); } return instance; } } public class Main { public static void main(String[] args) { // Accessing the Singleton instance ReflectionSafeSingleton singleton = ReflectionSafeSingleton.getInstance(); } }
Explanation:
The constructor checks if an instance already exists, and if it does, it throws an exception to prevent reflection-based instantiation.
Output:
ReflectionSafeSingleton instance created.
8. Singleton with Serialization Protection
When a Singleton class implements Serializable, it can create new instances during deserialization. To prevent this, override the readResolve() method to return the same instance.
Example:
import java.io.Serializable; public class SerializedSingleton implements Serializable { private static final long serialVersionUID = 1L; // Static instance of the Singleton class private static final SerializedSingleton INSTANCE = new SerializedSingleton(); // Private constructor to prevent instantiation private SerializedSingleton() { System.out.println("SerializedSingleton instance created"); } // Public method to provide access to the instance public static SerializedSingleton getInstance() { return INSTANCE; } // readResolve method to prevent creating a new instance during deserialization protected Object readResolve() { return getInstance(); } } public class Main { public static void main(String[] args) { // Accessing the Singleton instance SerializedSingleton singleton = SerializedSingleton.getInstance(); } }
Explanation:
The readResolve() method ensures that the same instance is returned during deserialization.
Output:
SerializedSingleton instance created.
Conclusion
The Singleton design pattern ensures that only one instance of a class is created and provides global access to that instance. In Java, there are several ways to implement the Singleton pattern:
Eager Initialization: The instance is created when the class is loaded.
Lazy Initialization: The instance is created when it is first accessed.
Thread-Safe Singleton: The synchronized keyword ensures only one thread creates the instance.
Double-Checked Locking: Minimizes the cost of synchronization with a double check.
Bill Pugh Singleton: Inner static class provides a lazy-loaded thread-safe Singleton.
Enum Singleton: The most robust and straightforward implementation.
Reflection and Serialization Safe: Methods to protect against breaking the Singleton pattern with reflection or serialization.
By choosing the appropriate method for your application's needs, you can implement Singleton classes effectively in Java.