Singleton pattern is a creational design pattern that ensures a class has only one instance and provides a global point of access to that instance.
The Singleton design pattern is one of the most commonly used patterns and a favorite topic in interviews. It opens up numerous follow-up questions, diving deeper into the intricacies of design patterns, coding skills, and multithreading—critical concepts for real-world applications.
Singletone design pattern is used only when only one instance of a class is required throughout the application. This instance is created upon its first access, and the same instance continues to be used until the application terminates.
Singleton is useful in situations where only one instance of a class should exist throughout the application's lifecycle. Common scenarios include:
public class EagerSingleton {
private static final EagerSingleton instance = new EagerSingleton();
private EagerSingleton() {
// Private constructor to prevent instantiation
}
public static EagerSingleton getInstance() {
return instance;
}
}
The instance is created even if the client application doesn't use it, which can be problematic. This is especially concerning if the class is establishing a database connection, file handle, or socket. If not managed correctly, it can lead to memory leaks. In such cases, it's better to use the lazy initialization pattern.
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {
// Private constructor to prevent instantiation
}
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
The instance is created only when needed, improving performance. However, this approach is not thread-safe. Multiple threads can create different instances if called simultaneously.
import java.lang.reflect.Constructor;
// Simple Singleton class
class Singleton {
private static final Singleton instance = new Singleton();
// Private constructor to prevent instantiation
private Singleton() {
// Private constructor
}
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
public class ReflectionBreakingSingleton {
public static void main(String[] args) {
try {
// Getting the Singleton instance normally
Singleton instanceOne = Singleton.getInstance();
// Using reflection to access the private constructor and create a new instance
Constructor constructor = Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true); // Bypassing private access
// Creating a new instance using reflection
Singleton instanceTwo = constructor.newInstance();
// Comparing both instances
System.out.println("Instance One: " + instanceOne.hashCode());
System.out.println("Instance Two: " + instanceTwo.hashCode());
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class Singleton {
private static Singleton instance;
private Singleton() {
if (instance != null) {
throw new RuntimeException("Use getInstance() method to get the single instance of this class.");
}
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
Using reflection can break Singleton, leading to multiple instances. Fixing this requires preventing reflection or using enum for Singleton implementation.
class Singleton {
private static Singleton instance = null;
// Private constructor to prevent instantiation from other classes
private Singleton() {
System.out.println("Singleton Instance Created");
}
// Lazy initialization method to return the singleton instance
public static Singleton getInstance() {
if (instance == null) { // Check if instance is null
try {
// Simulate a delay to make multithreading issue more apparent
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new Singleton(); // Create the instance if it doesn't exist
}
return instance;
}
}
public class MultiThreadedSingletonTest {
public static void main(String[] args) {
// Creating multiple threads to test the Singleton
Thread thread1 = new Thread(() -> {
Singleton singleton1 = Singleton.getInstance();
System.out.println("Thread 1 instance: " + singleton1.hashCode());
});
Thread thread2 = new Thread(() -> {
Singleton singleton2 = Singleton.getInstance();
System.out.println("Thread 2 instance: " + singleton2.hashCode());
});
// Start both threads
thread1.start();
thread2.start();
}
}
public class ThreadSafeSingleton {
private static ThreadSafeSingleton instance;
private static final Object lock = new Object();
private ThreadSafeSingleton() {
// Private constructor to prevent instantiation
}
public static ThreadSafeSingleton getInstance() {
if (instance == null) {
synchronized (lock) {
if (instance == null) {
instance = new ThreadSafeSingleton();
}
}
}
return instance;
}
}
public class StaticBlockSingleton {
private static StaticBlockSingleton instance;
private StaticBlockSingleton() {
// Private constructor to prevent instantiation
}
// Static block to initialize the Singleton instance
static {
try {
instance = new StaticBlockSingleton();
} catch (Exception e) {
throw new RuntimeException("Exception occurred in creating singleton instance");
}
}
public static StaticBlockSingleton getInstance() {
return instance;
}
}
class DoubleCheckedLockingSingleton {
// Volatile keyword ensures changes made in one thread are visible to others
private static volatile DoubleCheckedLockingSingleton instance;
// Private constructor to prevent instantiation
private DoubleCheckedLockingSingleton() {
System.out.println("Singleton instance created");
}
// Method to return the Singleton instance
public static DoubleCheckedLockingSingleton getInstance() {
if (instance == null) { // First check (no locking)
synchronized (DoubleCheckedLockingSingleton.class) {
if (instance == null) { // Second check (with locking)
instance = new DoubleCheckedLockingSingleton();
}
}
}
return instance;
}
}
public class DoubleCheckedLockingSingletonTest {
public static void main(String[] args) {
// Simulate multithreading environment
Thread thread1 = new Thread(() -> {
DoubleCheckedLockingSingleton singleton1 = DoubleCheckedLockingSingleton.getInstance();
System.out.println("Thread 1: Instance hashCode: " + singleton1.hashCode());
});
Thread thread2 = new Thread(() -> {
DoubleCheckedLockingSingleton singleton2 = DoubleCheckedLockingSingleton.getInstance();
System.out.println("Thread 2: Instance hashCode: " + singleton2.hashCode());
});
thread1.start();
thread2.start();
}
}