Skip to main content

Singleton Design Pattern

Problem​

You: Hey Shubh, I need to manage a database connection in my application. How should we approach this?

Shubh: We can create a new database connection object whenever we need one.

You: Sounds good. How would we ensure that we're not creating multiple connections unintentionally?

Shubh: We could manage it manually by ensuring that we reuse connections where possible and close them when done.

You: What if multiple parts of our application need access to the same database connection?

Shubh: We'd need to pass the connection object around or create a global reference to it, which can get messy.

Without Singleton Design
Pattern

Without Singleton Design Pattern

In the above image, there are multiple users who want to fetch data from the database. Each time they create a new instance to fetch the data and then close it (closing is important). At a given time, there can be multiple connections established. This approach consumes resources like CPU and memory, as objects are stored in RAM, thereby reducing performance. Additionally, the load on the database increases as there can be many instances interacting with it.

Solution​

You: Hey Shubh, I need to manage a database connection in my application. How should we approach this?

Shubh: Let's use the Singleton pattern. It ensures that only one instance of the database connection object exists throughout the application.

You: How does that help?

Shubh: We can centralize access to the database connection and ensure that all parts of the application use the same instance. This simplifies management, ensures consistency, prevents resource leaks, and makes efficient use of resources.

With Singleton Design Pattern

With Singleton Design Pattern

UML​

Shubh: Here's the UML class diagram for the Singleton Design Pattern.

Singleton Pattern UML

UML for Singleton Design Pattern

There are 5 key things to keep in mind:

  1. instance

  2. constructor

  3. getInstance()

  4. private

  5. static

Code​

DBConnection.java
public class DBConnection {

private static DBConnection connection;

private DBConnection(String name) {
System.out.println("Connection to DB is established by " + name);
}

public static DBConnection getDBConnection(String name) {
if (connection == null) {
connection = new DBConnection(name);
}
return connection;
}
}
  • Static: The connection variable is static so that we can access it inside the getDBConnection() method.

  • Private: The connection variable is private to prevent direct access and manipulation from outside.

  • Private Constructor: The constructor is private, ensuring that no new instances of DBConnection can be created from outside the class. Only getDBConnection() can create a new instance.

When getDBConnection() is called for the first time, it will instantiate the object using the new keyword. For subsequent calls, it will return the same object.

You: Thanks, Shubh. I understand the Singleton design pattern.

Multithreaded Code​

Shubh: Wait! Have you thought about what happens if two threads try to access getDBConnection() at the same time?

You: No, I haven't. What could go wrong?

Shubh: In a multithreaded environment, if two threads call getDBConnection() simultaneously, they might both see that connection is null and create new instances. This would violate the Singleton pattern.

You: Oh, I see. How can we fix that?

Shubh: You can use the synchronized keyword to ensure that only one thread can execute the critical section of code at a time. Also, you can use double-checked locking to improve performance. Here's how you can modify your code:

Main.java
public class DBConnection {

private static volatile DBConnection connection;

private DBConnection(String name) {
System.out.println("Connection to DB is established by " + name);
}

public static DBConnection getDBConnection(String name) {
if (connection == null){
synchronized (DBConnection.class){
if (connection == null) {
connection = new DBConnection(name);
}
}
}
return connection;
}
}

You: Can you explain how this works?

Shubh: Sure.

  • Double-Checked Locking: The getDBConnection() method first checks if connection is null. If it is, the method synchronizes on the DBConnection class and checks connection again. This double-check ensures that only one instance of DBConnection is created, even when multiple threads access the method simultaneously.

  • Synchronized Block: The synchronized block ensures that only one thread can enter the critical section (where the instance is created) at a time. This prevents multiple instances from being created.

  • Volatile Keyword: The volatile keyword ensures that changes to the connection variable are visible to all threads. This prevents the CPU from caching the connection variable, ensuring all threads see the most up-to-date value.

You: That makes sense. So this ensures that only one instance is created, right?

Shubh: Exactly. To test this, you can create a Runnable that gets the database connection and run it with multiple threads:

Main.java
public class Main {
public static void main(String[] args) {

Runnable runnable = new Runnable() {
@Override
public void run() {
DBConnection connection = DBConnection.getDBConnection(Thread.currentThread().getName());
}
};

Thread thread1 = new Thread(runnable, "Thread 1");
Thread thread2 = new Thread(runnable, "Thread 2");

thread1.start();
thread2.start();
}
}

You: So, when I run this, what should I expect to see?

Shubh: You should see that only one thread establishes the connection. The output will show something like:

Output: Connection to DB is established by Thread 1

This means the Singleton pattern is working correctly, ensuring only one instance is created and used by multiple threads.

You: Got it! This helps a lot. Thanks, Shubh!

Shubh: Anytime! The Singleton pattern is quite handy, especially when you need to manage resources like database connections efficiently in a multithreaded environment.