Overview
The Singleton pattern ensures that a class has only one instance throughout an application while providing a global access point to that instance. This is particularly useful for resource-intensive objects that should be shared, such as database connection pools, configuration managers, or logging utilities.
For example, SessionFactory in Hibernate manages session creation. Since creating a SessionFactory is expensive and an application typically needs only one instance, the Singleton pattern is the ideal choice.
Implementation Variants
Eager Initialization (Static Field)
public class SingletonTest01 {
public static void main(String[] args) {
Singleton instance = Singleton.getInstance();
Singleton instance1 = Singleton.getInstance();
System.out.println(instance == instance1); // true
System.out.println(instance.hashCode());
System.out.println(instance1.hashCode());
}
}
class Singleton {
private Singleton() {}
private static final Singleton INSTANCE = new Singleton();
public static Singleton getInstance() {
return INSTANCE;
}
}
Pros: Simple implementation with no thread safety concerns.
Cons: Instance is created at class loading time, potentially wasting memory if never used. This approach does not support lazy loading.
Eager Initialization (Static Block)
public class SingletonTest02 {
public static void main(String[] args) {
Singleton instance = Singleton.getInstance();
Singleton instance1 = Singleton.getInstance();
System.out.println(instance == instance1); // true
}
}
class Singleton {
private static final Singleton INSTANCE;
private Singleton() {}
static {
INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return INSTANCE;
}
}
Pros: Same benefits as the static field variant.
Cons: Same drawbacks—lacks lazy loading and may consume memory unnecessarily.
Lazy Initialization (Not Thread-Safe)
class Singleton {
private static Singleton INSTANCE;
private Singleton() {}
public static Singleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
Pros: Achieves lazy loading.
Cons: Unsafe in multithreaded environments. Multiple threads can pass the null check simultaneously and create separate instances.
Synchronized Method
class Singleton {
private static Singleton INSTANCE;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
Pros: Thread-safe.
Cons: Performance bottleneck. Every thread must acquire the monitor to check INSTANCE, even after initialization. Only the first call requires synchronization.
Synchronized Block (Incorrect)
class Singleton {
private static Singleton INSTANCE;
private Singleton() {}
public static Singleton getInstance() {
if (INSTANCE == null) {
synchronized (Singleton.class) {
INSTANCE = new Singleton();
}
}
return INSTANCE;
}
}
Pros: None in this form.
Cons: Still not thread-safe. A thread may pass the outer null check before another thread enters the synchronized block, resulting in multiple instances.
Double-Checked Locking
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;
}
}
Pros: Thread-safe, lazy-loading capable, and efficient. The outer check avoids synchronization after initialization, while the inner check prevents multiple instance creation.
Cons: Requires Java 5 or higher for correct volatile semantics.
Bill Pugh Singleton (Static Inner Class)
class Singleton {
private Singleton() {}
private static class Holder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return Holder.INSTANCE;
}
}
Pros: Thread-safe without synchronization. The JVM guarantees that the inner class loads only when getInstance() is called, and class initialization is inherently thread-safe.
Cons: None significant.
Enum Singleton
enum Singleton {
INSTANCE;
public void doSomething() {
System.out.println("Operation performed");
}
}
Pros: Extremely simple, inherently thread-safe, and protected against reflection and deserialization attacks.
Cons: Not suitable when the singleton must extend another class (enums cannot inherit other classes).
JDK Internals
The java.lang.Runtime class uses the eager initialization pattern:
public class Runtime {
private static final Runtime INSTANCE = new Runtime();
public static Runtime getRuntime() {
return INSTANCE;
}
}
Since the JVM always creates a Runtime instance, eager initialization is appropriate here.
When to Use
Apply the Singleton pattern when:
- Creating an object is expensive (database connections, file handles)
- Exactly one shared instance is required across the application
- A global access point is needed (configuration, logging)
Summary
| Variant | Thread-Safe | Lazy Load | Performence | Recommended |
|---|---|---|---|---|
| Eager (static field/block) | Yes | No | Excellent | When instance is always needed |
| Lazy (unsafe) | No | Yes | Excellent | Never in production |
| Synchronized method | Yes | Yes | Poor | Rarely |
| Synchronized block (broken) | No | Yes | Good | Never |
| Double-checked locking | Yes | Yes | Good | Yes |
| Static inner class | Yes | Yes | Excellent | Yes |
| Enum | Yes | Yes | Excellent | Yes |