An Introduction to Design Patterns and Singletons
Overview
Design patterns are used by object-oriented software developers to handle the problems faced during the development process. According to the book, Design Patterns-Elements of Reusable Object-Oriented Software, published by four authors; Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides (Gang of Four) in 1994, design patterns are divided into 3 categories. Refer to figure 1.
- Creational Pattern: Considers creating objects while hiding the logic behind them.
- Structural Pattern: Considers how classes inherit and how they are composed.
- Behavioral Pattern: Considers the communication between objects.
These are very useful for the developers (even inexperienced) because these are existing and well-defined solutions. As an example, the singleton design pattern can be used to implement the database connection for a standalone application.
Here I discuss the Singleton design pattern and hope to discuss the rest in the next articles.
Singleton Design Pattern
This comes under the creational pattern and this ensures the creation of a single instance of an object per JVM. The following example describes how we can apply the singleton design pattern.
Example;
Assume I need to create a manual log file per JVM. I can achieve this by creating a file in PDF format per JVM and logging the necessary details on it.
First, I have added the maven dependency itextpdf to the project on pom.xml. Refer to the following code.
Here Entity class defines the details of each instance and includes a method called setStatus() to add the details to the PDF. Refer to the following code.
In SingletonImplementation class, I have changed the access modifier of the constructor to private to ensure that no one can create an instance of SingletonImplementation class. If there are any interferences with the reflection APIs, it may cause the creation of the second instance of SingletonImplementation class with manual invoking. To avoid that, I have added a runtime exception inside the constructor. Here, we can declare the doc variable with and without the volatile keyword. But to explain thread-safe techniques, I have used the volatile keyword. Refer to the following code.
Here we can use both createLogger() and createLogger1() methods to create a single instance of the Document class.
When we consider the createLogger1() method; even though it is allowed to create a single instance of Document, at the 37th line, when the second thread is ready to execute (multithreading) and the first thread is still executing, the second thread will think that the doc is null and it will also begin to execute. This will lead to creating another instance of Document. This implies that the createLogger1() method is not thread-safe.
When we consider the createLogger() method; at line 19, when the second thread is ready to execute (multithreading) and the first thread is still executing, the second thread has to wait until the first thread leaves the block. After that, at the 21st line, when the second thread comes into the synchronized block, again it checks whether the doc is null or not. But in reality, the first thread has already created the object. Because of that, the second thread will leave the block and return the same instance which was created by the first thread. This is thread-safe and there are 2 checkpoints to check the singleton (at 19 and 21 lines), because of that this implementation is known as Double-checked locking.
At the main application, I have called the createLogger() method twice. Here, it has only taken 231 and 0 milliseconds to create both doc and doc1 objects respectively. Refer to the following code.
The corresponding outputs;
Document 1: com.itextpdf.text.Document@161cd475
Executed Time: 231
Document 2: com.itextpdf.text.Document@161cd475
Executed Time: 0
This indicates, the double-checked locking ensures the creation of single objects in a thread-safe manner.
Conclusions
- Design patterns are divided into 3 categories: creational, structural, and behavioral.
- Singleton is categorized under the creational design pattern.
- Singleton design pattern ensures the creation of a single instance of an object per JVM.
- Double-checked locking is used to lock the critical section of the code to make it thread-safe.
References