Thilan Dissanayaka Design Patterns Apr 26

Singleton Pattern explained simply

Ever needed just one instance of a class in your application? Maybe a logger, a database connection, or a configuration manager? This is where the Singleton Pattern comes in — one of the simplest but most powerful design patterns in software engineering.

This pattern involves a single class which is responsible to create an object while making sure that only single object gets created. This class provides a way to access its only object which can be accessed directly without need to instantiate the object of the class.

What is the Singleton Pattern?

At its core, the Singleton Pattern ensures that:

  • Only one instance of a class is ever created.
  • It provides a global access point to that instance.
  • You control the lifecycle of the object — no more unnecessary setups.

A Quick Example (Without Singleton)

Take this simple Logger class:

public class Logger {
    public Logger() {
        // Perform setup operations
        System.out.println("Logger setup operations performed.");
    }

    public void log(String message) {
        // Log the provided message
        System.out.println("Logging: " + message);
    }
}

public class MainProgram {
    public static Logger MyGlobalLogger = new Logger();

    public static void main(String[] args) {
        System.out.println("Main program started.");

        // Some code here...

        // We might not need to log anything, but the logger setup still occurs.

        // If we need to log something:
        MyGlobalLogger.log("This is a log message.");
    }
}

Here’s the problem: Even if we never log a message, the logger still gets created — wasting resources if the setup is heavy.

In the above code, the Logger object is created even if we never call the log() method. This is inefficient if the setup operations are resource-intensive.

⚡ Not ideal!

Enter Singleton Pattern

With Singleton, we create the object only when needed, and only once.

Here’s how:

public class Logger {
    private static Logger instance;

    // Private constructor to prevent instantiation from outside
    private Logger() {
        // Perform setup operations
        System.out.println("Logger setup operations performed.");
    }

    // Static method to get the singleton instance
    public static Logger getInstance() {
        if (instance == null) {
            instance = new Logger();
        }
        return instance;
    }

    public void log(String message) {
        // Log the provided message
        System.out.println("Logging: " + message);
    }
}

Next we can use this in our main program. Unlike in previous code we don't create an object. Access the logger through the getInstance() method

public static Logger MyGlobalLogger = Logger.getInstance();

MyGlobalLogger.log("This is a log message.");

What if we try to create an object like this?

public static Logger MyGlobalLogger = new Logger();

This will cause a compilation error because the constructor is private and cannot be accessed from outside the Logger class.

Why use lazy instantiation?

  • Objects are only created when it is needed
  • Helps control that we’ve created the Singleton just once.
  • If it is resource intensive to set up, we want to do it once.

Handling Multi-Threaded Access

What would happen if two different threads accessed this line at the same time?

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

In a multi-threaded environment, two threads could simultaneously pass the if (instance == null) check and create multiple instances. This breaks the Singleton pattern.

Synchronized Method (Thread-Safe but Slow)

public class Logger {
    private static Logger instance;

    private Logger() {
        System.out.println("Logger setup operations performed.");
    }

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

    public void log(String message) {
        System.out.println("Logging: " + message);
    }
}

This ensures thread safety but synchronizing the entire method may degrade performance.

Double-Checked Locking (Thread-Safe and Efficient)

public class Logger {
    private static volatile Logger instance;

    private Logger() {
        System.out.println("Logger setup operations performed.");
    }

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

    public void log(String message) {
        System.out.println("Logging: " + message);
    }
}
  • Volatile Keyword: Ensures visibility of changes to variables across threads.
  • Double Check: Reduces the need to synchronize after the object is created.

Local Variable Optimization: Reduces the need to access the volatile variable multiple times, improving performance.

public class Logger {
    private static volatile Logger instance;

    private Logger() {
        System.out.println("Logger setup operations performed.");
    }

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

    public void log(String message) {
        System.out.println("Logging: " + message);
    }
}

Use Cases for Singleton Pattern

  • Logging: Ensure consistent logging across the application using a single logger instance.

  • Database Connection: Manage a single connection pool object for handling database interactions.

  • Caching: Store frequently accessed data in memory for quick retrieval.

  • Configuration Management: Centralize application-wide configuration settings.

  • Thread Pools: Maintain a single thread pool instance to control and reuse worker threads.

  • Device Drivers: Ensure a single access point for hardware resources like printers or sensors.

  • Authentication Manager: Manage user sessions and authentication checks consistently.

  • Service Locator: Provide access to a globally available service without repeated instantiation.

The Singleton pattern is useful when you need a single, shared object to manage global state or resource-intensive operations. Among the different approaches, Bill Pugh Singleton Design is the most efficient and widely recommended approach for modern Java applications.

ALSO READ
Ballerina connector for Hubspot Schema API
Mar 23 Ballerina

Hi all, It's a new article on something cool. Here we are going to see how we can use the Hubspot schema connector with Ballerina. When it comes to building connectors for seamless integration....

GDB reverse engineering tutorial
Mar 23 Web App Hacking

hiii, I selected an interesting topic to discuss. Here, we are going to disassemble a binary file and take a look at what it does. This process is called reverse engineering. Let's run the program....

ACID Properties in Databases: The Key to Reliable Transactions
Apr 25 Database Systems

When working with databases, one thing is absolutely critical: keeping your data safe, consistent, and reliable. That's where ACID properties come in — a set of principles that ensure every....

Time based Blind SQL Injection
Apr 26 Web App Hacking

Blind SQL Injection happens when: There is a SQL injection vulnerability, BUT the application does not show any SQL errors or query outputs directly. In this case, an attacker has to ask....

Common Web Application Attacks
Apr 26 Web App Hacking

## SQL Injection (SQLi) What it is: SQL Injection happens when an attacker manipulates a web application's SQL queries by injecting malicious SQL code. If user inputs are not properly sanitized,....

Error based SQL Injection
Apr 26 Web App Hacking

In the previous example, we saw how a classic [SQL Injection Login Bypass](https://hacksland.net/sql-injection-login-bypass) works. SQL Injection is not all about that. The real fun is we can extract....