Dezign Patterns

Singleton Design Pattern

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

I) Singleton with double check locking

    public class Singleton {

        // Volatile is crucial to ensure visibility and prevent instruction reordering
        private static volatile Singleton instance;

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

        public static Singleton getInstance() {
            if (instance == null) { // First check (no locking) (T1,T2)
                synchronized (Singleton.class) {
                    if (instance == null) { // Second check (with locking)
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
  

Why Use volatile?

  • Without volatile, threads could see a "partially constructed object" due to "instruction reordering" during object creation (instance = new Singleton() is not atomic).
  • Volatile solves the visibility problem
  • volatile ensures that writes to instance are immediately visible to other threads.

II) Bill Pugh Singleton
  • It leverages the Initialization-on-demand holder idiom.
  • which makes use of the fact that static inner classes are not loaded into memory until they are referenced.

      public class Singleton {

          // Private constructor to prevent instantiation from outside the class
          private Singleton() {
          }

          // Static inner class to hold the instance of the Singleton
          private static class SingletonHelper {
              // The static instance will only be created when the class is accessed
              private static final Singleton INSTANCE = new Singleton();
          }

          // Public method to provide access to the Singleton instance
          public static Singleton getInstance() {
              return SingletonHelper.INSTANCE;
          }
      }
    
🚀 Advantages
  • Lazy initialization
  • thread safety
  • no synchronization overhead
  • efficient , the instance is guaranteed to be initialized only once due to the class loading mechanism
Note : this method works only in java due to it's class loading mechnsm , not work in other languages

III) Singleton using enum

Java Enums are inherently thread-safe, singleton, and serialization-safe. This makes them an ideal choice for implementing the Singleton pattern.

🚀 Advantages:
  • Simplicity: The code is clean and easy to understand.
  • Built-in Thread-Safety: No need for synchronized blocks or volatile variables.
  • Serialization Safety: The singleton property is automatically protected by the enum constant.
  • Deserialization Safety: Even when the singleton object is serialized and deserialized, the enum ensures that only one instance exists.

    mysql.url=jdbc:mysql://localhost:3306/mydb
    mysql.username=root
    mysql.password=password123

    postgresql.url=jdbc:postgresql://localhost:5432/mydb
    postgresql.username=admin
    postgresql.password=password456

    oracle.url=jdbc:oracle:thin:@localhost:1521:xe
    oracle.username=oracle
    oracle.password=oraclepass
    
Sample java code

import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
public enum DatabaseConnection {
    MYSQL("mysql"),
    POSTGRESQL("postgresql"),
    ORACLE("oracle");
    // Instance variables for the connection properties
    private String url;
    private String username;
    private String password;
    private boolean isConnected;
    // Path to the properties file (make sure the file is accessible)
    private static final String PROPERTIES_FILE = "db_config.properties";
    // Private constructor takes the name of the database type (e.g., "mysql")
    DatabaseConnection(String dbType) {
        loadProperties(dbType);
        this.isConnected = false;
    }
    // Method to load the properties for the given dbType
    private void loadProperties(String dbType) {
        try (FileInputStream fis = new FileInputStream(PROPERTIES_FILE)) {
            Properties properties = new Properties();
            properties.load(fis);
            // Load specific properties based on dbType (e.g., "mysql", "postgresql")
            this.url = properties.getProperty(dbType + ".url");
            this.username = properties.getProperty(dbType + ".username");
            this.password = properties.getProperty(dbType + ".password");
        } catch (IOException e) {
            System.out.println("Error loading properties file: " + e.getMessage());
        }
    }
    // Method to simulate a connection to the database
    public void connect() {
        if (!isConnected) {
            System.out.println("Connecting to " + name() + " at " + url);
            isConnected = true;
        } else {
            System.out.println(name() + " is already connected.");
        }
    }
    // Method to disconnect from the database
    public void disconnect() {
        if (isConnected) {
            System.out.println("Disconnecting from " + name());
            isConnected = false;
        } else {
            System.out.println(name() + " is not connected.");
        }
    }
    // Getter methods for connection details
    public String getUrl() {
        return url;
    }
    public String getUsername() {
        return username;
    }
    public String getPassword() {
        return password;
    }
    public boolean isConnected() {
        return isConnected;
    }
}
public class Main {
    public static void main(String[] args) {
        // Accessing each enum constant
        DatabaseConnection mysqlConnection = DatabaseConnection.MYSQL;
        DatabaseConnection postgresConnection = DatabaseConnection.POSTGRESQL;
        // Connecting to MySQL and PostgreSQL
        mysqlConnection.connect();  // Output: Connecting to MYSQL at jdbc:mysql://localhost:3306/mydb
        postgresConnection.connect();  // Output: Connecting to POSTGRESQL at jdbc:postgresql://localhost:5432/mydb
        // Disconnecting
        mysqlConnection.disconnect();  // Output: Disconnecting from MYSQL
        postgresConnection.disconnect();  // Output: Disconnecting from POSTGRESQL
    }
}
Properties can be loaded from DB as well, something like below

 // Method to load properties from the database
 private void loadPropertiesFromDatabase(String dbType) {
     try (Connection connection = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD)) {
         String query = "SELECT url, username, password FROM db_config WHERE db_type = ?";
         try (PreparedStatement stmt = connection.prepareStatement(query)) {
             stmt.setString(1, dbType);
             ResultSet rs = stmt.executeQuery();
             if (rs.next()) {
                 this.url = rs.getString("url");
                 this.username = rs.getString("username");
                 this.password = rs.getString("password");
             } else {
                 System.out.println("No configuration found for " + dbType);
             }
         }
     } catch (SQLException e) {
         System.out.println("Database error: " + e.getMessage());
     }
 }
IV) Singleton using AtomicReference

This method leverages AtomicReference to ensure atomicity of the Singleton instance creation. This approach can be seen as a custom lock-free solution for Singleton initialization.


import java.util.concurrent.atomic.AtomicReference;

public class Singleton {

 // AtomicReference to hold the Singleton instance
 private static final AtomicReference<Singleton> instance = new AtomicReference<>();
 // Private constructor prevents instantiation from other classes
 private Singleton() { }
 // Method to access the Singleton instance
 public static Singleton getInstance() {
     // Attempt to set the instance if it is not already set
     while (true) {
         Singleton current = instance.get();
         if (current != null) {
             return current;
         }
         // If the instance is null, attempt to set it
         Singleton newInstance = new Singleton();
         if (instance.compareAndSet(null, newInstance)) {
             return newInstance;
         }
     }
 }
}
V) Eager Initialization (Early Instantiation)

Potentially be wasteful if object is not used


    public class Singleton {

        // Eagerly initialized Singleton instance
        private static final Singleton instance = new Singleton();

        // Private constructor prevents instantiation from other classes
        private Singleton() { }

        // Method to access the Singleton instance
        public static Singleton getInstance() {
            return instance;
        }
    }
  • The Singleton instance is created when the class is loaded, ensuring thread-safety and no synchronization overhead.
  • Downside: This approach can be wasteful in terms of memory if the Singleton instance is never accessed during the execution of the program (since the instance is created eagerly at startup).
Usage/ Applications of Singleton Pattern :

This pattern can be used in any application that needs a global, shared resource, such as:

  • Database Connections (as shown above).
  • Configuration Manager for reading and managing settings.
  • Logging Utility.
  • Caching Singleton for shared resources in an application.