Easy Tutorial
❮ Adapter Pattern Home ❯

Singleton Pattern

The Singleton Pattern is one of the simplest design patterns in Java. This type of design pattern falls under the creational pattern category, providing one of the best ways to create an object.

This pattern involves a single class which is responsible for creating its own objects while ensuring that only a single object gets created. This class provides a way to access its only object which can be accessed directly without need to instantiate the object of the class.

Note:

Introduction

Intent: Ensure a class has only one instance and provide a global point of access to it.

Main Problem: A globally used class is frequently created and destroyed.

When to Use: When you want to control the number of instances and save system resources.

How to Solve: Check if the system already has this singleton; if yes, return it; if not, create it.

Key Code: The constructor is private.

Application Examples:

Advantages:

Disadvantages: No interface, cannot be inherited, conflicts with the single responsibility principle. A class should only care about internal logic, not how it is instantiated externally.

Usage Scenarios:

Caution: The getInstance() method needs to use synchronized (Singleton.class) to prevent multiple threads from entering simultaneously and causing multiple instantiations of instance.

Implementation

We will create a SingleObject class. The SingleObject class has its private constructor and a static instance of itself.

The SingleObject class provides a static method for external access to its static instance. The SingletonPatternDemo class uses the SingleObject class to get the SingleObject object.

Step 1

Create a Singleton class.

SingleObject.java

public class SingleObject {
    // Create an object of SingleObject
    private static SingleObject instance = new SingleObject();
    // Make the constructor private so that this class cannot be instantiated
    private SingleObject(){}
    // Get the only object available
    public static SingleObject getInstance(){
        return instance;
    }
    public void showMessage(){
        System.out.println("Hello World!");
    }
}

Step 2

Get the unique object from the singleton class.

SingletonPatternDemo.java

public class SingletonPatternDemo {
    public static void main(String[] args) {
        // Illegal constructor
        // Compile Time Error: The constructor SingleObject() is not visible
        // SingleObject object = new SingleObject();
        // Get the only object available
        SingleObject object = SingleObject.getInstance();
        // Show the message
        object.showMessage();
    }
}

Step 3

Execute the program, output result:

Hello World!

Several Ways to Implement Singleton Pattern

There are multiple ways to implement the Singleton pattern, as shown below:

1. Lazy Initialization, Thread-Unsafe

Is Lazy Initialization: Yes

Is Thread-Safe: No

Implementation Difficulty: Easy

Description: This is the most basic implementation. The biggest problem with this approach is that it does not support multithreading. It is not technically a Singleton pattern because it lacks synchronized locking.

Example

public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
    public static Singleton getInstance() {  
        if (instance == null) {  
            instance = new Singleton();  
        }  
        return instance;  
    }  
}

The following implementations support multithreading but have different performance characteristics.

2. Lazy Initialization, Thread-Safe

Is Lazy Initialization: Yes

Is Thread-Safe: Yes

Implementation Difficulty: Easy

Description: This approach has good lazy loading and works well in multithreading scenarios, but it is inefficient, as synchronization is rarely needed in 99% of cases.

Example

public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
    public static synchronized Singleton getInstance() {  
        if (instance == null) {  
            instance = new Singleton();  
        }  
        return instance;  
    }  
}

3. Eager Initialization

Is Lazy Initialization: No

Is Thread-Safe: Yes

Implementation Difficulty: Easy

Description: This approach is commonly used but can create garbage objects.

Example

public class Singleton {  
    private static Singleton instance = new Singleton();  
    private Singleton (){}  
    public static Singleton getInstance() {  
        return instance;  
    }  
}

4. Double-Checked Locking (DCL)

JDK Version: JDK1.5 and above

Is Lazy Initialization: Yes

Is Thread-Safe: Yes

Implementation Difficulty: Complex

Description: This approach uses a double-lock mechanism, which is safe and maintains high performance in multithreaded scenarios.

Example

public class Singleton {  
    private volatile static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
        if (singleton == null) {  
            synchronized (Singleton.class) {  
                if (singleton == null) {  
                    singleton = new Singleton();  
                }  
            }  
        }  
        return singleton;  
    }  
}

5. Static Inner Class

Is Lazy Initialization: Yes

Is Thread-Safe: Yes

Implementation Difficulty: Moderate

Description: This approach achieves the same effect as the double-checked locking method but is simpler to implement. It should be used for static fields, while the double-checked locking method can be used for instance fields requiring lazy initialization.

Example

public class Singleton {  
    private static class SingletonHolder {  
        private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
        return SingletonHolder.INSTANCE;  
    }  
}

6. Enum

JDK Version: JDK1.5 and above

Is Lazy Initialization: No

Is Thread-Safe: Yes

Implementation Difficulty: Easy

Description: This implementation method is not widely adopted yet, but it is the best way to implement the Singleton pattern. It is more concise, automatically supports serialization mechanisms, and absolutely prevents multiple instantiations.

Example

public enum Singleton {  
    INSTANCE;  
    public void whateverMethod() {  
    }  
}

Experience: Generally, the first and second lazy initialization methods are not recommended. The third eager initialization method is recommended. The fifth static inner class method should be used when explicit lazy loading is required. The sixth enum method can be tried when object deserialization is involved. The fourth double-checked locking method can be considered for other special needs.

❮ Adapter Pattern Home ❯