What is Singleton why we use it?
Gaddam Naveen
Java Developer | Spring Boot, Microservices, Full-Stack | @Medium Blogger
The Singleton pattern ensures that a class has only one instance throughout the application and provides a way to access that instance globally. This is useful when you need to control access to a shared resource, like a database connection or configuration settings, ensuring that only one instance manages it
Interviewer Question: “In Java, the Singleton pattern is often used to ensure that a class has only one instance. However, there are some scenarios where this pattern can be broken. Can you explain how the Singleton pattern can be broken in Java? What are the different ways that might lead to creating multiple instances of a class that is supposed to be a Singleton?”
Why We Use Singleton
Here’s a real-time example of how the Singleton pattern is used for resource management in the case of a database connection pool.
Imagine you have a Java web application that connects to a database to retrieve or store data. Opening and closing database connections is an expensive operation in terms of time and system resources. If each part of the application were to create its own database connection, it would lead to:
Solution Using Singleton for Database Connection Pooling
Using a Singleton pattern to manage a Database Connection Pool allows for efficient reuse of database connections rather than creating new ones every time.
Real-World Examples of Singleton Usage
How to create Basic Singleton (Lazy Initialization)
public class Singleton {
// Step 1: Private static instance
private static Singleton instance;
// Step 2: Private constructor to prevent instantiation from other classes
private Singleton() {
}
// Step 3: Public static method to provide access to the instance
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton(); // Lazy initialization
}
return instance;
}
}
There are several ways a Singleton pattern can be broken, each of which can have significant implications in different scenarios.
public class Singleton {
private static Singleton instance;
private Singleton() {
if (instance != null) {
throw new RuntimeException("Singleton instance already created!");
}
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
Breaking the Singleton using Reflection:
Singleton instance1 = Singleton.getInstance();
Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true); // Access private constructor
Singleton instance2 = constructor.newInstance(); // Break Singleton
System.out.println(instance1 == instance2); // Outputs: false
Consequence:
Solution:
2. Serialization and Deserialization
Scenario: When a Singleton object is serialized and then deserialized, it can lead to the creation of a new instance, breaking the Singleton pattern.
Example
public class Singleton implements Serializable {
private static final long serialVersionUID = 1L;
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
// Ensures that the instance is returned during deserialization
private Object readResolve() {
return getInstance();
}
}
Breaking the Singleton with Serialization:
Singleton instance1 = Singleton.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton.ser"));
oos.writeObject(instance1);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("singleton.ser"));
Singleton instance2 = (Singleton) ois.readObject();
System.out.println(instance1 == instance2); // Outputs: false
Consequence:
Solution
3. Cloning
Scenario: If a Singleton class implements the Cloneable interface, cloning the Singleton instance will create a new instance.
public class Singleton implements Cloneable {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
Breaking the Singleton with Cloning:
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = (Singleton) instance1.clone(); // Break Singleton
System.out.println(instance1 == instance2); // Outputs: false
Consequence:
Solution
4.Multi-Threading (Thread-Safety Issues)
Scenario: In a multi-threaded environment, improper synchronization during lazy initialization can lead to multiple instances of the Singleton being created.
Example (Incorrect double-checked locking\
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
Breaking the Singleton in Multi-Threading:
// Simulate multithreaded scenario
Thread thread1 = new Thread(() -> Singleton.getInstance());
Thread thread2 = new Thread(() -> Singleton.getInstance());
thread1.start();
thread2.start();
Consequence
Solution:
"Enjoyed reading? Follow me on Medium for more insightful articles on Java, Spring Boot, Hibernate, and more!"