0% found this document useful (0 votes)
8 views8 pages

Singleton Pattern

The document explains the Singleton design pattern, which restricts a class to a single instance and provides methods for its instantiation, including Eager Initialization, Lazy Initialization, Thread-Safe Initialization, and Double Locking. It highlights issues with Double-Checked Locking, such as instruction reordering and L1 caching, and presents the volatile keyword as a solution to ensure memory visibility and prevent instruction reordering. The document includes code implementations for each method and discusses the advantages and disadvantages of each approach.

Uploaded by

nimish sikri
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
8 views8 pages

Singleton Pattern

The document explains the Singleton design pattern, which restricts a class to a single instance and provides methods for its instantiation, including Eager Initialization, Lazy Initialization, Thread-Safe Initialization, and Double Locking. It highlights issues with Double-Checked Locking, such as instruction reordering and L1 caching, and presents the volatile keyword as a solution to ensure memory visibility and prevent instruction reordering. The document includes code implementations for each method and discusses the advantages and disadvantages of each approach.

Uploaded by

nimish sikri
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

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 }

Common questions

Powered by AI

L1 caching can cause issues whereby changes to the singleton instance variables in one CPU core's L1 cache are not immediately visible to other cores, leading to scenarios where different threads might see partially constructed objects. This can be mitigated by using the 'volatile' keyword, which ensures changes to a volatile variable are immediately visible to all threads by not allowing caching of its value in any core's cache .

Synchronized locking makes a singleton implementation thread-safe by allowing only one thread at a time to execute the getInstance method. However, it incurs a performance penalty as all threads sequentially wait for access. Double-checked locking improves performance by synchronizing only the initial creation of the instance, reducing the overhead once the instance is initialized. It requires careful handling of instruction reordering, typically using the 'volatile' keyword, to ensure correct behavior .

The 'volatile' keyword ensures that reads and writes to the singleton instance variable are directly from and to the main memory, bypassing CPU caches. This guarantees memory visibility across threads, preventing the simultaneous access and creation of multiple instances. It also prevents instruction reordering, enforcing a 'happens-before' relationship, ensuring full singleton object initialization before access by other threads, thus handling caching issues .

Double-checked locking addresses issues in synchronized singleton initialization by reducing unnecessary locking once the singleton instance is created. It synchronizes only during the initial check when the instance is null, allowing subsequent accesses to proceed without locking, thus optimizing performance. Careful handling of instruction reordering is crucial to avoid races by using the volatile keyword to guarantee full initialization visibility .

Lazy initialization affects the design by deferring instance creation until it's requested, potentially reducing initial startup time and resource usage when the instance might never be used. However, it complicates synchronization in multi-threaded environments, leading to possible issues with multiple instances unless synchronized correctly, often requiring additional measures like locking or double-checked locking to ensure thread safety .

Lazy initialization creates the singleton instance only upon a client's request, potentially leading to multiple singleton instances in a multi-threaded context if not managed properly. Eager initialization, however, initializes the singleton instance when the class is loaded, ensuring thread safety by avoiding race conditions at the cost of possibly increased startup time and memory use if the instance is never used .

The primary benefit of using the Singleton design pattern with thread-safe/synchronized initialization is ensuring a single instance in a multi-threaded environment, avoiding inconsistent states. However, the drawback is performance degradation due to thread contention, as synchronized methods require multiple threads to sequentially acquire locks, leading to potential bottlenecks .

Eager initialization ensures that the singleton instance is created at class loading time, which avoids synchronization costs and potential race conditions encountered in lazy or double-checked locking initializations. It guarantees that the instance is ready for use throughout the lifecycle of the application with minimal logic complexity. However, it consumes memory upfront and can extend startup time if the instance is resource-intensive to create .

Using exclusive locks in thread-safe singleton implementations, such as through synchronized methods, ensures atomicity and thread safety by serializing access to the singleton instance initialization. However, it leads to performance bottlenecks, especially under high contention from multiple threads, because all but one thread must wait, potentially leading to increased latency and decreased throughput in accessing the singleton .

Instruction reordering can cause a singleton object to appear partially initialized to other threads because the JVM might reorder object construction steps to optimize performance. The 'volatile' keyword addresses this by establishing memory barriers, preventing reordering around volatile reads and writes. This ensures other threads see a fully initialized object, maintaining the 'happens-before' relationship .

You might also like