Singleton#

The Singleton design pattern is a creational design pattern that ensures a class has only one instance and provides a global point of access to that instance. This pattern is useful in scenarios where you want to control the instantiation of a class to a single instance, such as when you need to manage a shared resource, a configuration manager, or a database connection pool.

Here’s the basic idea of the Singleton pattern:

  1. Private Constructor: The Singleton class has a private constructor, preventing direct instantiation of the class from outside.

  2. Static Instance: The class maintains a private static instance of itself. This instance is typically created the first time the Singleton class is accessed.

  3. Global Accessor: The Singleton class provides a public static method that returns the single instance of the class. This method is usually named something like getInstance().

To ensure thread safety and proper initialization while considering the class’s lifecycle, it’s important to follow these steps:

  1. Lazy Initialization: While creating the instance of the Singleton class, it’s common to use lazy initialization. This means the instance is created when it’s first requested rather than immediately when the class is loaded. This can improve performance and resource utilization.

  2. Double-Checked Locking (DCL): To ensure thread safety during lazy initialization, you can use the Double-Checked Locking technique. It involves checking if the instance is null before acquiring a lock. If the instance is null, then the lock is acquired, and the instance is created inside the synchronized block. This way, only the first thread that requests the instance will create it, and subsequent threads won’t need to acquire the lock.

  3. Volatility: Declare the reference to the instance as volatile to ensure that changes to it are visible across different threads. This prevents subtle issues that might occur due to thread caching and reordering.

Here’s an example implementation of the Singleton pattern in Java, following the best practices for thread safety and lazy initialization:

Singleton.java#
 public class Singleton {
     private static volatile Singleton instance;

     private Singleton() {
         // Private constructor to prevent instantiation.
     }

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

In this implementation:

  • The private constructor prevents direct instantiation.

  • The getInstance() method is used to retrieve the instance. It uses double-checked locking to ensure thread safety while initializing the instance.

  • The volatile keyword ensures that changes made to instance are visible across threads.

This implementation strikes a balance between thread safety, lazy initialization, and performance. It defers instance creation until the first call to getInstance(), which avoids unnecessary overhead at class loading time. The use of double-checked locking minimizes the synchronization overhead while still ensuring safe initialization in a multi-threaded environment.

Remember that the Singleton pattern should be used judiciously, as excessive use of global state can lead to tight coupling and make the codebase harder to test and maintain.

Bill Pugh’s Singleton implementation, also known as the “Initialization-on-demand holder idiom,” is an alternative and arguably a more elegant way to implement the Singleton pattern in Java. It leverages the initialization behavior of inner classes to achieve lazy initialization and thread safety without the complexities of double-checked locking.

Here’s the implementation:

Singleton.java#
public class Singleton {
    private Singleton() {
        // Private constructor to prevent instantiation.
    }

    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

Let’s understand why Bill Pugh’s implementation is considered better:

  1. Lazy Initialization: The inner class SingletonHolder is only loaded and initialized when getInstance() is called for the first time. This guarantees lazy initialization, ensuring that the instance is created only when needed.

  2. Thread Safety: Java’s class loading mechanism ensures that a class is loaded by only one thread. Therefore, the creation of the instance within the inner class is inherently thread-safe. This eliminates the need for explicit synchronization or double-checked locking, making the implementation simpler and less error-prone.

  3. Clean Code: The code is clean and straightforward. It separates the Singleton instance creation logic from the main Singleton class, enhancing code organization and readability.

  4. No Overhead: Unlike the traditional double-checked locking approach, Bill Pugh’s implementation doesn’t involve synchronization or volatile variables, which can introduce overhead. Instead, it relies on the class loader’s built-in synchronization during class initialization.

  5. Effective Singleton: The instance created using this approach is a genuine Singleton instance. It’s effectively immune to problems related to serialization, reflection, and classloader issues, as the inner class prevents direct instantiation of the Singleton class.

Here’s why Bill Pugh’s implementation is often preferred over other approaches:

  • It’s concise and doesn’t require additional synchronization mechanisms, which can be error-prone and negatively impact performance.

  • It provides a good balance between thread safety and lazy initialization.

  • It leverages the JVM’s class loading mechanism to ensure safe initialization without requiring the developer to handle synchronization explicitly.

  • It addresses common pitfalls associated with Singleton patterns, such as issues related to serialization and reflection.

Overall, Bill Pugh’s Singleton implementation simplifies the Singleton pattern by utilizing Java’s language features and guarantees to provide a clean, thread-safe, and efficient way to create Singleton instances.

Python#

In Python, the Singleton pattern is usually implemented using a combination of class attributes and the __new__ method, which is responsible for creating a new instance of the class. There’s no need for complex double-checked locking or initialization-on-demand holder idiom due to Python’s Global Interpreter Lock (GIL), which ensures that only one thread executes Python bytecode at a time. Here’s a common and straightforward implementation of the Singleton pattern in Python:

singleton.py#
from typing import TypeVar, Any

T = TypeVar('T')

class Singleton:
    _instance: T = None  # Class-level attribute to store the single instance

    def __new__(cls: T, *args: Any, **kwargs: Any) -> T:
        if cls._instance is None:
            from builtins import object
            cls._instance = object.__new__(cls)
        return cls._instance

In this implementation:

  • The __new__ method is overridden to control the instance creation process. If _instance is None, a new instance is created using the superclass’s __new__ method and stored in the _instance attribute.

  • Subsequent calls to __new__ return the previously created instance, ensuring that only one instance exists.

Here’s how you can use this implementation:

main.py#
 from singleton import Singleton

 s1 = Singleton()
 s2 = Singleton()

 assert s1 is s2  # should be true

The simplicity of this implementation makes it a common choice for implementing the Singleton pattern in Python. It provides a clear way to achieve the goal of having a single instance of a class while taking advantage of Python’s inherent thread-safety due to the GIL.

However, it’s worth mentioning that the Singleton pattern is often considered less necessary in Python due to the dynamic nature of the language and its easy support for global state. Depending on the use case, other design patterns or Pythonic approaches might be more suitable.

--- Bonus: Singleton as decorator ---