In this post, we will learn the principles of the Singleton design pattern, explore different ways to implement it, and discuss some best practices for using it.
The Singleton design pattern restricts the instantiation of a class, ensuring that only one instance of the class exists within the Java Virtual Machine (JVM). A Singleton class provides a global access point through which the single instance of the class can be accessed.
The Singleton design pattern is commonly used for logging, database driver objects, caching, configuration management, and thread pools. It is also used by several other design patterns, such as Abstract Factory, Builder, Prototype, and Facade.To implement the Singleton Design Pattern, the following principles should be followed:
Private constructor – Prevents other classes from creating new instances of the class.
Private static instance reference – Holds the single instance of the class and prevents external modification.
Public static method – Provides a global access point to retrieve the Singleton instance.
Below are the different approaches for implementing the Singleton design pattern.
In eager initialization, the Singleton instance is created when the class is loaded into memory. This is the simplest way to implement the Singleton design pattern. However, it has one drawback: the instance is created even if the client application never uses it, which may result in unnecessary memory usage. Below is the implementation of the Singleton class using eager initialization.
public class EagerInitializer{
private static final EagerInitializer instance = new EagerInitializer();
//private constructor to avoid client applications to use constructor.
private EagerInitializer(){
}
public static EagerInitializer getInstance(){
return instance;
}
}
If your Singleton class does not consume many resources, eager initialization is a suitable approach. However, in most real-world scenarios, Singleton classes are used to manage resource-intensive objects such as file systems, database connections, or configuration managers. In such cases, it is better to delay the creation of the instance until the client invokes the getInstance() method.Another limitation of eager initialization is that it does not provide flexibility for handling exceptions that may occur during instance creation.
-
Static block initialization
The static block initialization approach is similar to eager initialization, except that the Singleton instance is created inside a static initialization block. This approach provides the flexibility to handle exceptions that may occur during instance creation.
public class StaticInitializer {
private static StaticInitializer instance ;
//private constructor to avoid client applications to use constructor.
private StaticInitializer() {
}
static {
try {
instance = new StaticInitializer();
} catch(Exception e) {
throw new RuntimeException("Exception in static block");
}
}
public static StaticInitializer getInstance() {
return instance;
}
}
Both eager initialization and static block initialization create the Singleton instance before it is actually needed. In many cases, this is not considered a best practice because the instance is created even if it is never used. In the following sections, we'll explore how to implement a Singleton class that supports lazy initialization.
The lazy initialization approach creates the Singleton instance only when it is requested through the global access method (getInstance()). This ensures that the instance is created only when it is actually needed.Below is a sample implementation of the Singleton design pattern using the lazy initialization approach.
public class LazyInitializer{
private static LazyInitializer instance;
//private constructor to avoid client applications to use constructor.
private LazyInitializer(){
}
public static LazyInitializer getInstance(){
if(instance == null){
instance = new LazyInitializer();
}
return instance;
}
}
The above implementation works well in a single-threaded environment. However, in a multithreaded environment, it can cause issues if multiple threads enter the if block simultaneously. In such a scenario, multiple instances of the Singleton class may be created, violating the Singleton design pattern and causing different threads to obtain different instances.In the next section, we'll explore different approaches for implementing a thread-safe Singleton class.
The simplest way to make a Singleton class thread-safe is to synchronize the global access method (getInstance()). This ensures that only one thread can execute the method at a time, preventing multiple instances from being created concurrently.A typical implementation of this approach is shown below.
public class ThreadSafe{
private static ThreadSafe instance ;
//private constructor to avoid client applications to use constructor.
private ThreadSafe(){
}
public static synchronized ThreadSafe getInstance(){
if(instance == null){
instance = new ThreadSafe();
}
return instance;
}
}
The above implementation is thread-safe and works correctly. However, it can reduce performance because every call to the getInstance() method requires synchronization, even after the Singleton instance has already been created. In practice, synchronization is only needed when the instance is created for the first time.To avoid this unnecessary synchronization overhead, the Double-Checked Locking pattern is used. In this approach, the instance is checked before entering the synchronized block and checked again inside the synchronized block. This ensures that synchronization occurs only during the initial creation of the Singleton instance while maintaining thread safety.
The following code snippet demonstrates the Double-Checked Locking implementation.
public static ThreadSafe getInstance(){
if(instance == null){
synchonized(ThreadSafe.class){
if(instance == null ){
instance = new ThreadSafe();
}
}
}
return instance;
}
JDBC Example using Singleton Design pattern:--
A common real-world example of the Singleton design pattern is a ConnectionFactory class. This class is implemented as a Singleton and contains the database connection configuration along with methods for creating database connections. The reason for making the ConnectionFactory class a Singleton is that only one instance of the factory is required throughout the application. This single factory instance can then be used to create multiple database Connection objects whenever needed—one factory, many connections.
package com.adnjavainterview;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class ConnectionFactory {
//static reference to itself
private static ConnectionFactory instance = new ConnectionFactory();
public static final String URL = "jdbc:mysql://localhost/jdbcdb";
public static final String USER = "YOUR_DATABASE_USERNAME";
public static final String PASSWORD = " YOUR_DATABASE_PASSWORD";
public static final String DRIVER_CLASS = "com.mysql.jdbc.Driver";
//private constructor
private ConnectionFactory() {
try {
Class.forName(DRIVER_CLASS);
}
catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
private Connection createConnection() {
Connection connection = null;
try {
connection = DriverManager.getConnection(URL, USER, PASSWORD);
}
catch (SQLException e) {
System.out.println("ERROR: Unable to Connect to Database.");
}
return connection;
}
public static Connection getConnection() {
return instance.createConnection();
}
}
Thank you for visiting blog..
Related Post:--
Web Server
A web server primarily serves static resources and forwards dynamic requests to an application server if needed.
Responsibilities:
Serves static content (HTML, CSS, JavaScript, images, PDFs, etc.)
Handles HTTP/HTTPS requests
Supports SSL/TLS termination
Performs URL rewriting and request routing
Can act as a reverse proxy and load balancer
Examples:
Apache HTTP Server
Nginx
Microsoft IIS
Application Server
An application server executes the application's business logic and generates dynamic content.
Responsibilities:
Executes Java, .NET, or other server-side applications
Processes business logic
Connects to databases
Manages transactions and security
Generates dynamic responses
Supports technologies such as Servlets, JSP, EJB, and REST APIs
Examples:
Apache Tomcat
JBoss EAP
IBM WebSphere Application Server
Oracle WebLogic Server
Key Differences
In Spring Boot
With Spring Boot, the distinction is often less visible because it includes an embedded servlet container such as Apache Tomcat by default. This allows you to package and run your application without installing a separate application server.
For production environments, many organizations still place a web server such as Nginx or Apache HTTP Server in front of the Spring Boot application to handle SSL termination, load balancing, caching, and reverse proxying.