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

  1. 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 == null check, resulting in multiple instances.
  2. Lazy Initialization with Synchronized Method (Thread-Safe but Inefficient)

    • Uses synchronized to 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;
    }
    
  3. 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).
  4. 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.
  5. 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 volatile to 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).