The Singleton Pattern

The Singleton Pattern

A single instance of a class with a global reference. [creational pattern]

There are scenarios where only a single object instantiation is necessary, such as for caches and objects used for logging. In such cases, instantiating more than one could lead to various issues, including incorrect program behaviour, excessive resource usage, and inconsistent results.

A class can be instantiated any number of times by using just the new keyword with the constructor. How can we ensure that only a single object is allowed to be created for a class?

  • Can we use a private constructor?

public class MyClass {

    private MyClass() {}
}        

  • Now, it’s not even possible to create a single object. We can exclusively invoke the MyClass constructor through any MyClass methods. To utilize a MyClass method, an object of the MyClass class must be created. However, instantiation of the MyClass class is restricted as we are unable to call the constructor.
  • Do we necessarily have to instantiate the MyClass class to call any of its methods? What about any static methods?

public class MyClass {
  
    public static MyClass getInstance() {}
}        

  • We can invoke the static method getInstance() without the need to instantiate the MyClass class, as static methods belong to the class [MyClass.getInstance()].
  • Now, let’s utilize this static method to instantiate the MyClass class.

public class MyClass {

    private MyClass() {}
    
    public static MyClass getInstance() {
      return new MyClass();
    }

}        

  • It does seem interesting, doesn’t it? We can create an instance of MyClass even with a private constructor, but does this ensure a single instance of the MyClass class? No.

Let’s make a slight adjustment to the above approach to guarantee a single instance of the MyClass class.

  • Let’s create a global static variable capable of holding an instance of the MyClass class, and utilize this global variable to return whenever the getInstance() method is called. Now, it appears that we can ensure a single instance of the MyClass class. Okay, let’s change the name of the class to Singleton now, as we are ensuring only one instance of the class.

public class Singleton {
  
    private static Singleton uniqueInstance;
    
    private Singleton() {}
    
    public static Singleton getInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
    
    // other methods -> singleton class is same as any other class.
}        

  • In the getInstance() method, if the uniqueInstance is null, we haven’t instantiated the Singleton class yet; otherwise, we have already instantiated it, and we can directly return the existing instance. This way, we can ensure only a single instance of the Singleton class.

https://refactoring.guru/design-patterns/singleton

  • We are taking a class and allowing it to manage a single instance of itself. We are also preventing any other class from creating a new instance independently.
  • We are providing a global access point to the instance; whenever you need an instance, just query the class, and it will provide you with the single instance.
  • If we never need a Singleton instance, we never create it. This approach is known as lazy instantiation, which is particularly crucial for resource-intensive objects.

The Singleton Pattern ensures that a class has only one instance and provides a global point of access to it.
Class Diagram of Singleton pattern



Dealing with Multithreading

Alright, now with the Singleton class, we ensure the existence of only one instance of the class. However, in a multi-threaded environment, does this functionality still hold, and is it truly thread-safe?

  • Let’s consider two threads calling the getInstance() method simultaneously. If both check that uniqueInstance is null at the same time, both may proceed to create two new instances of the Singleton class, potentially breaking the Singleton pattern.
  • To address the issue of multiple threads potentially creating multiple instances simultaneously in a multi-threaded environment, synchronization mechanisms or techniques like double-check locking can be employed.

Using the synchronized keyword can ensure that only one thread at a time can execute a particular block of code.

public class Singleton {

    private static Singleton uniqueInstance;

    private Singleton() {}

    public static synchronized Singleton getInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }

    // other methods -> singleton class is same as any other class.
}        

  • By introducing the synchronized keyword to getInstance(), we force each thread to wait its turn, allowing only one thread to enter the method at a time. This ensures that no two threads can access the method simultaneously.
  • Using the Synchronized keyword has resolved the issue, and our Singleton class is now thread-safe, ensuring the creation of only one instance at any cost. However, the Synchronized keyword can be costly, as all threads calling the getInstance() method must wait until the previous one is completed.
  • The only time synchronization is needed is for the first invocation of the method. Once we set the uniqueInstance variable to an instance of Singleton, there is no further need to synchronize the method. After the initial invocation, it becomes an overhead. Is there a way to further optimize this?

Move to an eagerly created instance rather than one created lazily.

  • We can proceed to create an instance of Singleton in a static initializer. This guarantees thread safety. In this approach, we rely on the JVM to create the unique instance of Singleton when the class is loaded. The JVM ensures that the instance is created before any thread attempts to access it.

public class Singleton {

    private static Singleton uniqueInstance = new Singleton();

    private Singleton() {}

    public static synchronized Singleton getInstance() {
        return uniqueInstance;
    }
    
}        

  • Eager instantiation has its drawbacks and is not ideal, especially if the class is heavyweight and its necessity is uncertain.

Double-checked locking to reduce to use of synchronization in getInstance()

  • Using double-checked locking, we first check if the instance is already created before applying synchronization. This way, we only synchronize the first time, and subsequently, we return the unique instance without making any threads wait.

public class Singleton {

    private volatile static Singleton uniqueInstance;

    private Singleton() {}

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

  • The synchronized block ensures that only one thread can enter this block at a time. Other threads attempting to enter this block will be blocked until the lock is released.
  • The purpose of the volatile keyword is to guarantee the visibility of the instance variable to all threads, thereby preventing memory visibility issues.
  • When the instance variable is declared as volatile, it ensures that modifications to this variable are immediately visible to all threads. In the absence of the volatile keyword, there is no assurance that changes made by one thread will be promptly visible to other threads, potentially resulting in complications in multithreaded environments.

Singleton with enums

  • Using an enum is the simplest way to implement a singleton. Employing enums for singletons is a recommended approach when seeking a straightforward, thread-safe, and efficient implementation without the necessity for double-checked locking or other synchronization mechanisms.

public enum SingletonEnum {
    INSTANCE; // The single instance of the SingletonEnum

    // Add other methods or fields as needed
    public void doSomething() {
        System.out.println("SingletonEnum is doing something.");
    }
}

public class SingletonExample {
    public static void main(String[] args) {
        // Access the singleton instance
        SingletonEnum singleton = SingletonEnum.INSTANCE;

        // Use the singleton instance
        singleton.doSomething();
    }
}        

Referenced from the book ‘Head First Design Patterns’ by Eric Freeman, Elisabeth Robson, Kathy Sierra, and Bert Bates.

要查看或添加评论,请登录

Kannuru Dinesh的更多文章

  • The Strategy Pattern

    The Strategy Pattern

    Defines a family of algorithms, and makes them interchangeable. [behavioural pattern] Let's consider that we are…

  • Content Delivery Network

    Content Delivery Network

    CDN speeds up web content delivery by caching data closer to users. Imagine you are hosting a website in New York…

  • CAP Theorem

    CAP Theorem

    Consistency, Availability, Partition tolerance - Pick two. Consider two nodes, N1 and N2, where the data is initially…

    1 条评论

社区洞察

其他会员也浏览了