Implementing Thread-Safe Singleton Pattern
Implementing Thread-Safe Singleton Pattern
Problem Description
The Singleton pattern is a common design pattern that ensures a class has only one instance and provides a global access point to it. In a multi-threaded environment, it is necessary to guarantee the thread safety of the singleton to avoid creating multiple instances. This problem requires implementing a thread-safe Singleton pattern and explaining its principles.
Key Knowledge Points
- Lazy Initialization vs. Eager Initialization
- Double-Checked Locking
- Static Inner Class
- The role of the volatile keyword
Solution Steps
-
Basic Lazy Initialization (Not Thread-Safe)
- Delays instance creation, but may create duplicate instances under multiple threads.
public class Singleton { private static Singleton instance; private Singleton() {} // Private constructor public static Singleton getInstance() { if (instance == null) { // Thread A and B may enter this condition simultaneously instance = new Singleton(); } return instance; } }- Issue: Multiple threads may simultaneously pass the
instance == nullcheck, resulting in multiple instances.
-
Lazy Initialization with Synchronized Method (Thread-Safe but Inefficient)
- Uses
synchronizedto ensure thread safety, but locks on every call, resulting in poor performance.
public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } - Uses
-
Double-Checked Locking (DCL)
- Checks whether the instance exists twice—once before locking and once after—to avoid unnecessary lock contention.
public class Singleton { private static volatile Singleton instance; // Must use volatile private Singleton() {} public static Singleton getInstance() { if (instance == null) { // First check synchronized (Singleton.class) { // Lock if (instance == null) { // Second check instance = new Singleton(); } } } return instance; } }- Role of volatile: Prevents instruction reordering, ensuring other threads do not obtain partially initialized objects (refer to JVM's "instruction reordering" issue).
-
Static Inner Class (Recommended Solution)
- Utilizes the JVM class loading mechanism to guarantee thread safety while achieving lazy loading.
public class Singleton { private Singleton() {} private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.INSTANCE; // Inner class is loaded on first call } }- Principle: JVM class loading is thread-safe. The static inner class is only loaded when
getInstance()is called for the first time, and static variable initialization occurs only once.
-
Enum Singleton (The Most Concise Thread-Safe Solution)
- Enum instances are guaranteed uniqueness by the JVM and can prevent reflection attacks.
public enum Singleton { INSTANCE; public void doSomething() { ... } }
Summary
- Double-Checked Locking in lazy initialization requires careful use of
volatileto avoid instruction reordering. - The static inner class solution requires no explicit synchronization, making the code concise and efficient.
- The enum singleton is recommended by Effective Java, though it may lack flexibility in certain scenarios (e.g., when inheriting from other classes).