Elegant and Efficient Singleton Implementations in Java
This article explores various Java singleton patterns—including eager initialization, static blocks, lazy initialization, double‑checked locking, volatile variables, static inner‑class holder, and serialization handling—explaining their mechanisms, thread‑safety concerns, and how to choose the most appropriate implementation for robust, high‑performance backend systems.
When reading source code, developers often encounter singleton patterns used for resources such as database connections, thread pools, or Spring containers, and may wonder about their exact behavior and safety. This article discusses how to implement elegant, efficient, and data‑safe singletons in Java.
Simple eager initialization (getInstance)
Many classes expose a public static MySingleton getInstance() method that simply returns a pre‑created static instance. The code looks like:
public class MySingleton {
private static MySingleton instance = new MySingleton();
private MySingleton() {}
public static MySingleton getInstance() {
return instance;
}
}Clients use MySingleton singleton = MySingleton.getInstance();. The static field guarantees a single instance bound to the class.
Complex creation with static initialization block
When the singleton depends on other beans, a static block can enforce a strict serial order, preventing JVM reordering and ensuring thread safety:
public class MySingleton {
private static MySingleton instance = null;
private static OtherSingleton helper = null;
static {
helper = OtherSingleton.getInstance();
instance = new MySingleton(helper);
}
private MySingleton(OtherSingleton helper) {
if (helper) this.helper = helper;
else throw new MyException("OtherSingleton getInstance failed");
}
public static MySingleton getInstance() {
return instance;
}
}This guarantees that the dependent OtherSingleton is available before the main singleton is created.
Lazy ("懒汉式") initialization
The lazy approach creates the instance only when first requested:
public class MySingleton {
private static MySingleton instance = null;
private static OtherSingleton helper = null;
private MySingleton(OtherSingleton helper) { /* same as above */ }
public static MySingleton getInstance() {
if (instance == null)
instance = new MySingleton(OtherSingleton.getInstance());
return instance;
}
}However, in a multithreaded environment two threads may simultaneously pass the if (instance == null) check, creating two instances.
Synchronizing the whole method
Adding synchronized to the method serializes access, but incurs a performance penalty because the lock is held for every call:
public static synchronized MySingleton getInstance() {
if (instance == null)
instance = new MySingleton(OtherSingleton.getInstance());
return instance;
}Double‑checked locking
To reduce lock contention, the method first checks without locking, then synchronizes only when the instance is still null:
public static MySingleton getInstance() {
if (instance == null) {
synchronized (MySingleton.class) {
if (instance == null) {
instance = new MySingleton(OtherSingleton.getInstance());
}
}
}
return instance;
}Because of possible visibility issues, the instance field should be declared volatile to ensure that other threads see the fully constructed object.
Volatile‑based implementation
public class MySingleton {
private volatile static MySingleton instance = null;
private static OtherSingleton helper = null;
private MySingleton(OtherSingleton helper) { /* same as above */ }
public static MySingleton getInstance() {
if (instance == null) {
synchronized (MySingleton.class) {
if (instance == null) {
instance = new MySingleton(OtherSingleton.getInstance());
}
}
}
return instance;
}
}Static inner‑class holder (Initialization‑on‑demand holder)
This technique leverages the JVM’s class‑loading guarantees to achieve lazy, thread‑safe initialization without explicit synchronization:
public class MySingleton {
private MySingleton(OtherSingleton helper) { /* same as above */ }
private static class MySingletonAdapter {
private static final MySingleton instance = new MySingleton(OtherSingleton.getInstance());
}
public static MySingleton getInstance() {
return MySingletonAdapter.instance;
}
}Serializable singleton
When a singleton implements Serializable, deserialization creates a new instance unless readResolve() returns the existing one:
public class MySingleton implements Serializable {
private static final long serialVersionUID = -3453453414141241L;
private static MySingleton instance = new MySingleton(OtherSingleton.getInstance());
private MySingleton(OtherSingleton helper) { /* same as above */ }
private Object readResolve() {
return instance;
}
}Dependency‑injection (IoC) perspective
Complex dependency graphs can cause circular initialization problems; using an Inversion‑of‑Control container with dependency injection helps manage the creation order and resolve such cycles.
Overall, the article provides a comprehensive guide to selecting and implementing the most suitable singleton pattern in Java, balancing simplicity, performance, and thread safety.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
