Implementing Singleton Patterns: Eager vs Lazy Initialization
The Singleton pattern ensures a class has only one instance and provides a global point of access to it. Instead of relying on global variables—which can be instantiated multiple times—the class itself controls the creation and storage of its sole instance, guaranteeing uniqueness.
Class Structure
The Singleton class defines a static method, getInstance(), which returns the single instance. The constructor is private to prevent external instantiation.
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
This implementation uses double-checked locking to reduce synchronization overhead. The volatile keyword ensures visibility across threads and prevents reordering issues during object construction.
Thread Safety Considerations
Without synchronization, concurrent access from multiple threads may result in multiple instances being created. The synchronized block ensures that only one thread creates the instance, while the inner check prevents redundant instantiation after the first thread completes initialization.
Static Initialization (Eager Loading)
For scenarios where performance is critical and the cost of lazy initialization is unacceptable, use eager loading:
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
In this approach, the instance is created when the class is loaded by the JVM, eliminating the need for runtime cheecks or synchronization. This method is simpler and inherently thread-safe but may lead to unnecessary memory usage if the instance is never used.
Lazy Initialization (Lazy Loading)
Alternatively, delay instance creation until the first call to getInstance():
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {}
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
While this conserves resources, it requires careful handling in multithreaded environments to avoid race conditions.
Choose between eager and lazy loading based on application requirements: prefer eager loading for predictable, high-performance systems; use lazy loading when resource efficiency is prioritized and instantiation cost is significant.