Design Patterns in Action
About Lesson

Singleton Design Pattern

 

The Singleton design pattern ensures that the class has only one instance and provides a global point of access to it. It is mostly used when only one instance of an object is required throughout the lifetime of an application. It ensures that a single instance of a class is initialized at the time of first access and the same instance is reused in subsequent access.  

 

UML class diagram for Singleton

 

 

There are various strategies through which we can get a single instance of a class. These strategies ensure that only one instance of the class exists throughout the JVM. 

 

Let’s consider a real-world scenario where a ConfigurationManager class is implemented as a Singleton.

 

  

 

1. Eager Initialization Strategy

 

In the eager initialization strategy, the instance of a singleton class is created at the time of class loading. In this technique instance of class is created much before it is required. The major drawback of eager initialization is the instance is created irrespective of whether it is required at runtime or not. As mostly in this case instance is created at system start-up, and it is left unused. If the cost of creating the instance is not too large in terms of resources/time this approach is best.

 

Here, ConfigurationManager class is implemented as a Singleton using eager initialization. The ConfigurationManager is responsible for managing application settings globally.

 

public class ConfigurationManager {

    // Step 1: Create a private static instance variable
    private static final ConfigurationManager instance = new ConfigurationManager();

    // Application settings
    private String databaseUrl;
    private String apiKey;

    // Step 2: Make the constructor private to prevent external instantiation
    private ConfigurationManager() {
        // Private constructor to prevent instantiation
        // Initialize default settings
        this.databaseUrl = "jdbc:mysql://localhost:3306/mydatabase";
        this.apiKey = "default-api-key";
    }

    // Step 3: Provide a public static method to access the single instance
    public static ConfigurationManager getInstance() {
        return instance;
    }

    // Getter and setter methods for application settings
    public String getDatabaseUrl() {
        return databaseUrl;
    }

    public void setDatabaseUrl(String databaseUrl) {
        this.databaseUrl = databaseUrl;
    }

    public String getApiKey() {
        return apiKey;
    }

    public void setApiKey(String apiKey) {
        this.apiKey = apiKey;
    }

    // Other methods related to configuration management
    // ...

    // Example method
    public void displayConfiguration() {
        System.out.println("Database URL: " + databaseUrl);
        System.out.println("API Key: " + apiKey);
    }
}

 

In this example:

– The ConfigurationManager class is a Singleton, ensuring that there is only one instance throughout the application.

– The Singleton instance is eagerly initialized with default settings when the class is loaded.

– The getDatabaseUrl() and getApiKey() methods provide global access to the application settings.

– The setDatabaseUrl(String databaseUrl) and setApiKey(String apiKey) methods allow modifying the configuration.

– The displayConfiguration() method is an example method that prints the current configuration.

 

2. Lazy Initialization Strategy

 

This technique delays the creation of a singleton instance and ensures that a singleton instance is created only when needed. As a result, the singleton ConfigurationManager instance is not created until its getInstance() method is called for the first time.

 

class ConfigurationManager {

    // Step 1: Create a private static instance variable (not initialized)
    private static ConfigurationManager instance;

    // Application settings
    private String databaseUrl;
    private String apiKey;

    // Step 2: Make the constructor private to prevent external instantiation
    private ConfigurationManager() {
        // Private constructor to prevent instantiation
        // Initialize default settings
        this.databaseUrl = "jdbc:mysql://localhost:3306/mydatabase";
        this.apiKey = "default-api-key";
    }

    // Step 3: Provide a public static method to access the single instance with lazy initialization
    public static ConfigurationManager getInstance() {
        // Lazy initialization: create the instance only if it's null
        if (instance == null) {
            instance = new ConfigurationManager();
        }
        return instance;
    }

    // Getter and setter methods for application settings
    public String getDatabaseUrl() {
        return databaseUrl;
    }

    public void setDatabaseUrl(String databaseUrl) {
        this.databaseUrl = databaseUrl;
    }

    public String getApiKey() {
        return apiKey;
    }

    public void setApiKey(String apiKey) {
        this.apiKey = apiKey;
    }

    // Other methods related to configuration management
    // ...

    // Example method
    public void displayConfiguration() {
        System.out.println("Database URL: " + databaseUrl);
        System.out.println("API Key: " + apiKey);
    }

    public void printConfigurationManager() {
        System.out.println("ConfigurationManagerInfo - " +
                this.getClass().getSimpleName() + " - " + 
                this.hashCode());
    }
}

 

This code works very well in a single-threaded environment. In a multi-threaded environment, there is a possibility that multiple instances of singleton class get created. Consider a scenario where two threads (say, t1 and t2) call getInstance() at the same time. Let’s suppose t1 is preempted just after it enters if block and control of execution is given to t2. Thus, both threads will get a new instance of the singleton class.

 

Let’s see how the Client code creates multiple instances of ConfigurationManager in a multi-threaded environment.

 

public class Client {
    public static void main(String[] args) {
        // Code breaks in multithreaded environment
        for(int i = 0; i < 10; i++){
            new Thread(() -> {
                ConfigurationManager instance = ConfigurationManager.getInstance();
                instance.printConfigurationManager();
            }).start();
        }
    }
}

 

In the Client class main method, getInstance() has been accessed by 10 threads simultaneously. All 10 threads print the ConfigurationManager hashcode on the console (as shown in the output below). The output window shows two threads that have bypassed the ‘if’ block in the getInstance() method simultaneously. Thus, two instances are created for the singleton ConfigurationManager class with hashcode – 1227810188 and 1161390873.

 

Output:

 

ConfigurationManagerInfo - ConfigurationManager - 1227810188
ConfigurationManagerInfo - ConfigurationManager - 1227810188
ConfigurationManagerInfo - ConfigurationManager - 1161390873
ConfigurationManagerInfo - ConfigurationManager - 1227810188
ConfigurationManagerInfo - ConfigurationManager - 1227810188
ConfigurationManagerInfo - ConfigurationManager - 1212245309
ConfigurationManagerInfo - ConfigurationManager - 1227810188
ConfigurationManagerInfo - ConfigurationManager - 1227810188
ConfigurationManagerInfo - ConfigurationManager - 1227810188
ConfigurationManagerInfo - ConfigurationManager - 1227810188

 

To overcome the concurrency problem, let’s synchronize getInstance().

 

3. Lazy Initialization Strategy (synchronized method)

 

In this technique getInstance() method is made thread-safe by applying the synchronized keyword at the method level. It prevents threads from creating multiple instances of ConfigurationManager singleton class. (showing only changes to the getInstance() method)

 

public static synchronized ConfigurationManager getInstance() {
    // Lazy initialization: create the instance only if it's null
    if (instance == null) {
        instance = new ConfigurationManager();
    }
    return instance;
}

 

Upon running the Client class (same as above) it resolves the concurrency problem as we have made the getInstance() method synchronized.

 

Output:

 

ConfigurationManagerInfo - ConfigurationManager - 1212245309
ConfigurationManagerInfo - ConfigurationManager - 1212245309
ConfigurationManagerInfo - ConfigurationManager - 1212245309
ConfigurationManagerInfo - ConfigurationManager - 1212245309
ConfigurationManagerInfo - ConfigurationManager - 1212245309
ConfigurationManagerInfo - ConfigurationManager - 1212245309
ConfigurationManagerInfo - ConfigurationManager - 1212245309
ConfigurationManagerInfo - ConfigurationManager - 1212245309
ConfigurationManagerInfo - ConfigurationManager - 1212245309
ConfigurationManagerInfo - ConfigurationManager - 1212245309

 

The problem with the synchronized keyword at the getInstance() method level is that it is expensive. Each access to the getInstance() method will acquire a lock by the thread on the ConfigurationManager object. This approach brings low performance to the system. Ideally, the lock on the object should be acquired for the first time the getInstance() method is called, but in reality, the code is locking the object every time the getInstance() method is called. To solve this problem, instead of synchronizing at the method level, we synchronize at the block (critical section) level. See code below –

 

4. Lazy Initialization Strategy (synchronized block)

 

In this technique getInstance() method is made thread-safe by applying the synchronized keyword at the block level. (showing only changes to the getInstance() method)

 

public static ConfigurationManager getInstance() {
    // Lazy initialization: create the instance only if it's null
    if (instance == null) {
        synchronized (ConfigurationManager.class) {
            instance = new ConfigurationManager();
        }
    }
    return instance;
}

 

The synchronized keyword is now provided at the block level. This prevents multiple threads from waiting for object lock at the method level. Now, they wait for object lock just for the critical piece of execution. However, the above code is still not thread-safe.

 

Consider a scenario – Let’s say thread t1 enters the synchronized block and before it can assign a new ConfigurationManager instance to the static instance variable, the thread t1 is preempted. Meanwhile, thread t2 enters the if block, as the static instance variable is still null. Now thread t2 will wait for thread t1 to finish executing synchronized block. Once thread t1 comes out of the synchronized block it releases its lock. Now thread t2 enters the synchronized block by acquiring the lock and starts executing it. As a result, both threads t1 and t2 at the end will wind up having different instances of ConfigurationManager singleton class. See output below –

 

Upon running the Client class (same as above) the output shows concurrency issues with two instances of ConfigurationManager as – 703779748 and 1484158691.

 

Output:

 

ConfigurationManagerInfo - ConfigurationManager - 703779748
ConfigurationManagerInfo - ConfigurationManager - 1484158691
ConfigurationManagerInfo - ConfigurationManager - 703779748
ConfigurationManagerInfo - ConfigurationManager - 703779748
ConfigurationManagerInfo - ConfigurationManager - 703779748
ConfigurationManagerInfo - ConfigurationManager - 703779748
ConfigurationManagerInfo - ConfigurationManager - 703779748
ConfigurationManagerInfo - ConfigurationManager - 389589790
ConfigurationManagerInfo - ConfigurationManager - 703779748
ConfigurationManagerInfo - ConfigurationManager - 703779748

 

To solve this problem, we generally go for a double-checked locking strategy. See code below –

 

5. Lazy Initialization Strategy (double-checked locking)

 

This strategy provides thread-safe lazy initialization of the singleton class. The idea behind this strategy is that we provide a null check on the static instance variable again inside the synchronized block. This ensures thread safety from the scenario discussed above. Thread t2 will always have to go through a null check once again to create a new ConfigurationManager instance. Thus, it will always find static instance to not be null and will get back the same instance of the ConfigurationManager singleton class. The code for double-checked locking is as follows –

 

public static ConfigurationManager getInstance() {
    if (instance == null) { // first null check
        synchronized (ConfigurationManager.class) {
            if (instance == null) { // second null check
                instance = new ConfigurationManager();
            }
        }
    }
    return instance;
}

 

Upon running the Client class (same as above) it resolves the concurrency problem as we have provided double-checked locking.

 

Output:

 

ConfigurationManagerInfo - ConfigurationManager - 1212245309
ConfigurationManagerInfo - ConfigurationManager - 1212245309
ConfigurationManagerInfo - ConfigurationManager - 1212245309
ConfigurationManagerInfo - ConfigurationManager - 1212245309
ConfigurationManagerInfo - ConfigurationManager - 1212245309
ConfigurationManagerInfo - ConfigurationManager - 1212245309
ConfigurationManagerInfo - ConfigurationManager - 1212245309
ConfigurationManagerInfo - ConfigurationManager - 1212245309
ConfigurationManagerInfo - ConfigurationManager - 1212245309
ConfigurationManagerInfo - ConfigurationManager - 1212245309

 

 

In a real-world scenario, you might use this Singleton ConfigurationManager to manage and access application-wide settings such as database connection URLs and API keys from various parts of your application.

 

 

Join the conversation
0% Complete