Singleton
Definition
Class Diagram
Implementation of Singleton Pattern
1. Eager Initialization
2. Lazy Initialization
3. Thread-Safe/Synchronized Intialization
4. Double Locking
Issue with Double-Checked Locking
Issue 1: Instruction Reordering
Issue 2: L1 Caching
The Correct Solution: Using the volatile keyword
Resources
Video → 27. All Creational Design Patterns | Prototype, Singleton, Fac
tory, AbstractFactory, Builder Pattern
Video → 28. BUG in Double-Checked Locking of Singleton Pattern & i
ts Fix | Low Level System Design Question
Definition
The Singleton design pattern is used when we have to create only ONE instance of a class. This is useful when
exactly one object is needed to perform and coordinate many actions across the system. A Singleton class ensures
that there is only one instance of itself, regardless of the number of clients attempting to access it.
Class Diagram
Implementation of Singleton Pattern
There are 4 ways to implement the Singleton Pattern, each with its advantages and disadvantages.
1. Eager Initialization
One of the simplest implementations of the Singleton Pattern.
The instance is created when the class is loaded.
Make the constructor private → prevents instantiation by other classes.
Mark the singleton instance field as:
static variable → this ensures there's only one class instance shared across all instances of the class.
final variable → prevents the singleton instance from being reassigned after initialization.
Implementation
1 // 1. Eager Initialization - Singleton
2 public class DBConnectionEager {
3
4 // The single instance, created immediately
5 private static final DBConnectionEager instance = new
DBConnectionEager();
6
7 // The private constructor prevents instantiation
8 private DBConnectionEager() {
9 }
10
11 // Method to return the unique instance of this class
12 public static DBConnectionEager getInstance() {
13 return instance;
14 }
15
16 // Method to display a message
17 public void displayMessage() {
18 [Link]("Eager Initialization - Singleton - " +
this);
19 }
20 }
21 // Test Singleton Implementation
22 public class SingletonDemo {
23 public static void main(String[] args) {
24 [Link]("====== Testing Eager Initialization
======");
25 // SingletonObject obj = new SingletonObject(); -->
Compilation error
26 // Get the unique instance of SingletonObject
27 DBConnectionEager eager1 = [Link]();
28 DBConnectionEager eager2 = [Link]();
29 // Display the message
30 [Link]();
31 [Link]();
32 // Check if the instances are the same
33 [Link]("Same instance? " + (eager1 == eager2));
//true
34 }
35 }
2. Lazy Initialization
The instance is created only once when the client requests it.
Make the constructor private → prevents instantiation by other classes.
It is not thread-safe. When multiple threads access the getInstance method for the first time, there is a
chance of multiple singleton instances being created.
Mark the singleton instance field as:
static variable → this ensures there's only one class instance shared across all instances of the class.
Implementation
1 // 2. Lazy Initialization Singleton
2 public class DBConnectionLazy {
3
4 private static DBConnectionLazy instance = null;
5
6 // The private constructor prevents instantiation
7 private DBConnectionLazy() {
8 }
9
10 // Singleton Object is created only when it is required
11 // This method returns the unique instance of this class
12 // Drawback: This implementation is not thread-safe.
13 public static DBConnectionLazy getInstance() {
14 if (instance == null) {
15 instance = new DBConnectionLazy();
16 }
17 return instance;
18 }
19
20 // Method to display a message
21 public void displayMessage() {
22 [Link]("Lazy Initialization - Singleton - " +
this);
23 }
24 }
25 // Test Singleton Implementation
26 public class SingletonDemo {
27 public static void main(String[] args) {
28 [Link]("====== Testing Lazy Initialization ======");
29 // Get the unique instance of SingletonObject
30 DBConnectionLazy lazy1 = [Link]();
31 DBConnectionLazy lazy2 = [Link]();
32 // Display the message
33 [Link]();
34 [Link]();
35 // Check if the instances are the same
36 [Link]("Same instance? " + (lazy1 == lazy2)); //
true
37 }
38 }
3. Thread-Safe/Synchronized Intialization
This is similar to Lazy Initialization
It is made thread-safe by using the synchronized keyword in the getInstance method, ensuring
only one thread can execute this method at a time.
This will ensure only one singleton instance is created when multiple threads are invoking the same
getInstance method.
But this process of singleton instantiation can be expensive because when a thread enters the synchronized
method, it acquires a lock on the class object, does the job, and releases the lock for other threads to acquire it
and execute the method.
Imagine 100 threads invoking the synchronized getInstance method simultaneously; 99 will have to
wait until the first thread finishes its execution.
Implementation
1 // 3. Thread Safe Singleton
2 public class DBConnectionThreadSafe {
3
4 // Singleton Object is created only when it is required
5 private static DBConnectionThreadSafe instance = null;
6
7 // Private Constructor to avoid client applications from using the
constructor
8 private DBConnectionThreadSafe() {
9 }
10
11 // Thread Safe Method to return the unique instance of this class
12 public static synchronized DBConnectionThreadSafe getInstance() {
13 if (instance == null) {
14 instance = new DBConnectionThreadSafe();
15 }
16 return instance;
17 }
18
19 // Method to display a message
20 public void displayMessage() {
21 [Link]("Thread Safe Singleton - " + this);
22 }
23 }
24 // Test Singleton Implementation
25 public class SingletonDemo {
26 public static void main(String[] args) {
27 [Link]("====== Testing Thread Safe ======");
28 // Get the unique instance of SingletonObject
29 DBConnectionThreadSafe threadSafe1 =
[Link]();
30 DBConnectionThreadSafe threadSafe2 =
[Link]();
31 // Display the message
32 [Link]();
33 [Link]();
34 // Check if the instances are the same
35 [Link]("Same instance? " + (threadSafe1 ==
threadSafe2)); //true
36 }
37 }
4. Double Locking
It is a more optimised version of thread-safe singleton object instantiation.
This method reduces performance overhead from synchronization(as seen previously) by only synchronizing the
block of code when the singleton object is initially created(the first time). Upon instantiation, the other/future
threads do not have to enter the synchronized block. Thus, making it faster.
Widely used in the industry.
Implementation
1 // 4. Double Locking Singleton
2 public class DBConnectionDoubleLocking {
3
4 // Double Locking Singleton instance variable
5 private static DBConnectionDoubleLocking instance = null;
6
7 // Private constructor
8 private DBConnectionDoubleLocking() {
9 }
10
11 // Thread Safe Method to return the unique instance of this class
12 public static DBConnectionDoubleLocking getInstance() {
13 if (instance == null) { // first check
14 synchronized ([Link]) {
15 if (instance == null) { // second check
16 instance = new DBConnectionDoubleLocking();
17 }
18 }
19 }
20 return instance;
21 }
22
23 // Method to display a message
24 public void displayMessage() {
25 [Link]("Double Locking Singleton - " + this);
26 }
27 }
28 // Test Singleton Implementation
29 public class SingletonDemo {
30 public static void main(String[] args) {
31 [Link]("====== Testing Double Locking ======");
32 // Get the unique instance of SingletonObject
33 DBConnectionDoubleLocking doubleLocking1 =
[Link]();
34 DBConnectionDoubleLocking doubleLocking2 =
[Link]();
35 // Display the message
36 [Link]();
37 [Link]();
38 // Check if the instances are the same
39 [Link]("Same instance? " + (doubleLocking1 ==
doubleLocking2)); // true
40
41 }
42 }
Issue with Double-Checked Locking
Video explanation → 28. BUG in Double-Checked Locking of Singleton Pattern & its Fix | Low Level System Design Quest
ion
Consider the following example with a member variable portNumber :
1 // Singleton - Double Checked Locking - Issue Demo
2 public class DBConnectionDoubleCheckedLockIssue {
3
4 // Double Locking Singleton instance variable
5 private static DBConnectionDoubleCheckedLockIssue connectionObj =
null;
6 int portNumber;
7
8 // Private constructor
9 private DBConnectionDoubleCheckedLockIssue(int portNumberValue) {
10 portNumber = portNumberValue;
11 }
12
13 // Thread Safe Method to return the unique instance of this class
14 public static DBConnectionDoubleCheckedLockIssue
getConnectionObj() {
15 if (connectionObj == null) { // First check
16 synchronized ([Link]) {
17 if (connectionObj == null) { // Second check
18 connectionObj = new
DBConnectionDoubleCheckedLockIssue(5567);
19 }
20 }
21 }
22 return connectionObj;
23 }
24
25 // Method to display a message
26 public void displayMessage() {
27 [Link]("Singleton - Double Checked Locking - Issue
- " + this);
28 }
29 }
The above highlighted step(creation of object) involves the following steps:
1. Allocating memory
2. Initializing the object with all member variables
3. Assigning the reference
Issue 1: Instruction Reordering
The JVM can reorder the object construction steps to achieve better performance. The JVM might assign the
reference before fully initializing the object, causing other threads to see a partially constructed object.
While T1 is executing the reordered instructions, when T1 is executing L3, let’s say T2 performs the first
check, it gets the connectionObject i.e. NOT NULL and the portNumber holds the default
value(after execution of L2 by T1) and proceeds with performing operations using the partially constructed
connectionObject , which can result in an error as T1 is yet to initialize the portNumber (member
variable).
Issue 2: L1 Caching
Without proper synchronization, changes made by one thread may not be visible to other threads because of CPU
caching and memory models.
In a multicore CPU, each CPU core has its own cache; sometimes, cores might not synchronize their caches,
leading to inconsistent views of memory. When T1 creates a fully constructed connectionObject and
saves it in the L1 cache, and if another thread T2 requests a connectionObject before the Caches
have been synchronised and the changes have been saved in memory, T2 will proceed to create another
singleton connectionObject instance and perform operations with it. This will lead to multiple
singleton instances.
The Correct Solution: Using the volatile keyword
Understanding the volatile keyword
When a variable is declared as volatile, it instructs the JVM and the compiler to handle that variable in a
specific way. This modifier is primarily used in multithreaded programming to ensure memory visibility,
consistency of shared variables across different threads, and instruction ordering.
How do volatile keyword properties solve the issues with double-checked locking?
Memory Visibility Guarantee: All reads and writes of a volatile variable are always performed directly to
and from the main memory, and all write operations are immediately visible to all threads. Without
volatile , threads might cache variables locally and not see updates from other threads. [Solves Issue 2]
Instruction Re-ordering: The volatile keyword provides guarantees to prevent specific types of
instruction reordering, as illustrated below. This prevention is accomplished by establishing "happens-before"
guarantees, which effectively create memory barriers or fences. These barriers stop the compiler and CPU from
reordering instructions around volatile reads or writes, even if such reordering would enhance
performance in a single-threaded context.
[Solves Issue 1]
Implementation: Using the volatile keyword
1 // Singleton - Double Checked Locking - Fix Demo
2 public class DBConnectionDoubleCheckedLockFix {
3
4 // volatile keyword is used to ensure that the value of the
variable
5 // is fetched from the memory every time.
6 private static volatile DBConnectionDoubleCheckedLockFix
connectionObj = null;
7
8 int portNumber;
9
10 private DBConnectionDoubleCheckedLockFix(int portNumberValue) {
11 portNumber = portNumberValue;
12 }
13
14 // Thread Safe Method to return the unique instance of this class
15 public static DBConnectionDoubleCheckedLockFix
getConnectionObj(int portNumberValue) {
16 if(connectionObj == null) {
17 synchronized([Link]) {
18 if(connectionObj == null) {
19 connectionObj = new
DBConnectionDoubleCheckedLockFix(portNumberValue);
20 }
21 }
22 }
23 return connectionObj;
24 }
25
26 // Method to display a message
27 public void displayMessage() {
28 [Link]("Singleton - Double Checked Locking - Fix -
" + this);
29 }
30 }