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.
- 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;
}
}
- Lazy initialization
- thread safety
- no synchronization overhead
- efficient , the instance is guaranteed to be initialized only once due to the class loading mechanism
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
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
}
}
// 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());
}
}
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;
}
}
}
}
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).
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.