All you need to know about Singleton pattern

Jigyasa
5 min readMay 25, 2024

--

Miss. Yash is interested in learning about the Singleton pattern. Let’s begin by exploring three fundamental questions: What is the Singleton pattern, Why is it used, and How is it implemented?

What?

A singleton class is a design pattern that restricts the instantiation of a class to one single instance. This is useful when exactly one object is needed to coordinate actions across the system. The singleton pattern ensures that a class has only one instance and provides a global point of access to that instance.

Why?

Imagine a small town with only one lighthouse. This lighthouse is crucial because it guides all ships safely into the harbor, especially during foggy nights and stormy weather. The townspeople recognize the importance of having just one lighthouse, as multiple lighthouses might give conflicting signals, leading to confusion and potential shipwrecks.

One stormy night, the lighthouse keeper, Mr. Singleton, realized the perfect metaphor for a problem he had been facing in his software development job. His team had been struggling with managing configurations across their application. Different parts of the application were using different configuration files, leading to inconsistency and bugs. It was as chaotic as having multiple lighthouses with different signals.

Drawing inspiration from his nightly duty, Mr. Singleton decided to implement a single configuration manager for their application — a singleton class. Just as the town had one lighthouse guiding all the ships, their application would have one central configuration manager guiding all its modules.

He created a class where the configuration was loaded only once, and any part of the application needing access to these settings would use this single instance.

  1. Controlled Access to a Single Instance: The singleton pattern ensures that a class has only one instance, providing a controlled access point.
  2. Global Access: The singleton instance can be accessed globally, making it useful for coordinating actions in a system.
  3. Resource Management: It is often used for managing resources that need to be shared, such as configuration settings, logging, or database connections.

How?

Here is an example in Java:

1. Classic approach

public class Singleton {
// The single instance of the class
private static Singleton instance;

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

// Public method to provide access to the instance
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

Good to learn the implementation part. Wait a second, Yash wants to know if above implementation is thread safe? NO

Now, imagine the following sequence of events in a multi-threaded environment:

  1. Thread A checks if instance is null, finds that it is.
  2. Before Thread A can create the instance, it gets paused by the scheduler.
  3. Thread B also checks if instance is null and finds it null as well.
  4. Thread B proceeds to create a new instance and assigns it to instance.
  5. Thread A gets scheduled again and creates another instance, unaware that an instance already exists.
  6. Now, we have two instances of the singleton class, violating the singleton pattern.

Lack of Synchronization

The lack of synchronization in the classic implementation is the root cause of this issue. When multiple threads enter the getInstance() method concurrently, there's no mechanism to prevent them from creating multiple instances if instance is found to be null by more than one thread simultaneously.

Solution: Adding Synchronization

To address this issue, synchronization can be added to the getInstance() method to ensure that only one thread can create the instance at a time. Here's how it can be done:

2. Synchronized Method

public class Singleton {
private static Singleton instance;

private Singleton() {}

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

Above implementation is thread safe. However multiple threads may experience delays waiting to acquire the lock( Synchronization locks the entire method).

Yash wants to know if can we make thread safe implementation more effective. Well , you can make non-critical sections of the method (e.g., returning the instance) to execute concurrently without synchronization overhead. Here is how you achieve that:

3. Synchronized Block Inside Method

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

The overhead of synchronization is incurred only when creating the instance, rather than for the entire method. This can lead to improved performance compared to the earlier approach, especially if there are frequent calls to getInstance() from multiple threads.

Yash: Are there any other approaches to implement singleton class?

Yes, of course.

4. Double-Checked Locking

Double-checked locking is a technique that aims to reduce the overhead of synchronization by only locking the critical section when necessary. Here’s how it can be implemented in Java:

public class Singleton {
private static volatile Singleton instance;

private Singleton() {}

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

In this approach, the getInstance() method first checks if the instance is null without synchronization. If it is, it enters a synchronized block to ensure that only one thread can create the instance. However, since most of the time the instance will already be initialized after the first check, subsequent calls to getInstance() will not incur the overhead of synchronization.

5. Static Initialization

Static initialization ensures that the singleton instance is created when the class is loaded, thus providing thread safety without the need for synchronization.

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

private Singleton() {}

public static Singleton getInstance() {
return instance;
}
}

With static initialization, the singleton instance is created when the class is loaded by the JVM, regardless of whether it’s needed at that moment. This means that the instance is created eagerly, even if it might not be used during the application’s execution. In scenarios where resources are limited or the creation of the instance is expensive, this eager initialization can be inefficient.

Considerations:

Each approach to implementing the Singleton pattern has its own trade-offs in terms of performance, thread safety, and complexity. When choosing an implementation, it’s important to consider factors such as the expected concurrency level, performance requirements, and ease of understanding and maintaining the code.

Conclusion:

These additional approaches to implementing the Singleton pattern provide alternative ways to achieve thread safety and ensure that only one instance of a class is created. By understanding the strengths and weaknesses of each approach, you can choose the one that best fits the requirements of your application.

Yash is happy as she learnt singleton design pattern. Thanks for reading !!

--

--