Understanding the Singleton Design Pattern in Java: Implementations, Thread Safety, and Best Practices
This article explains the Singleton design pattern in Java, covering its definition, lazy and eager implementations, thread‑safety issues such as double‑checked locking and volatile, and alternative approaches like static inner classes and enums, providing code examples and practical insights.
The Singleton pattern ensures that a class has only one instance throughout the application. It is commonly used in Java, but naïve implementations can break the singleton guarantee under multithreaded conditions.
Lazy (on‑demand) Singleton
Simple lazy version creates the instance when getInstance() is first called.
// Version 1
public class Single1 {
private static Single1 instance;
public static Single1 getInstance() {
if (instance == null) {
instance = new Single1();
}
return instance;
}
}Making the constructor private prevents external instantiation.
// Version 1.1
public class Single1 {
private static Single1 instance;
private Single1() {}
public static Single1 getInstance() {
if (instance == null) {
instance = new Single1();
}
return instance;
}
}In a multithreaded environment, two threads can simultaneously pass the if (instance == null) check and create two instances, violating the pattern.
Synchronized version
// Version 2
public class Single2 {
private static Single2 instance;
private Single2() {}
public static synchronized Single2 getInstance() {
if (instance == null) {
instance = new Single2();
}
return instance;
}
}Adding synchronized guarantees only one thread creates the instance, but it serializes all calls to getInstance() , hurting performance.
Double‑checked locking version
// Version 3
public class Single3 {
private static Single3 instance;
private Single3() {}
public static Single3 getInstance() {
if (instance == null) {
synchronized (Single3.class) {
if (instance == null) {
instance = new Single3();
}
}
}
return instance;
}
}This reduces synchronization overhead by locking only when the instance is null, but without additional safeguards it can still fail because object creation is not an atomic operation and the JVM may reorder instructions.
Volatile version (the “ultimate” fix)
// Version 4
public class Single4 {
private static volatile Single4 instance;
private Single4() {}
public static Single4 getInstance() {
if (instance == null) {
synchronized (Single4.class) {
if (instance == null) {
instance = new Single4();
}
}
}
return instance;
}
}Declaring instance as volatile prevents instruction reordering and ensures a memory barrier so that a partially constructed object is never observed by another thread.
Eager (class‑loading) Singleton
// Eager implementation
public class SingleB {
private static final SingleB INSTANCE = new SingleB();
private SingleB() {}
public static SingleB getInstance() {
return INSTANCE;
}
}The instance is created when the class is loaded, which the JVM synchronizes automatically, eliminating the need for explicit locks but potentially wasting resources if the instance is never used.
Other idiomatic approaches
Effective Java (1st edition) recommends a static inner‑class holder:
// Static inner‑class holder
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton() {}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}This combines lazy loading with the thread‑safety guarantees of class loading.
Effective Java (2nd edition) suggests using a single‑element enum:
// Enum singleton
public enum SingleInstance {
INSTANCE;
public void fun1() {
// do something
}
}
// Usage
SingleInstance.INSTANCE.fun1();Enum singletons are inherently serializable and protected against reflection attacks, making them a concise and robust solution, though they cannot be extended.
Summary
While the basic singleton idea appears simple, correct implementation in Java requires careful handling of multithreading, instruction reordering, and object initialization. Various patterns—synchronized methods, double‑checked locking with volatile , eager initialization, static holder classes, and enums—offer trade‑offs between simplicity, performance, and safety.
Java Captain
Focused on Java technologies: SSM, the Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading; occasionally covers DevOps tools like Jenkins, Nexus, Docker, ELK; shares practical tech insights and is dedicated to full‑stack Java development.
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.