Back to: Java Design Patterns
Singleton Design Pattern in Java
In this article, I am going to discuss the Singleton Design Pattern in Java with Real-Time Examples. Please read our previous article where we discussed Abstract Factory Design Patterns in Java with Examples. The Singleton Design Pattern in Java falls under the Creational Pattern Category. As it belongs to the creational pattern category, it is going to be dealing with object creation and manipulation.
What is a Singleton Design Pattern?
In software development, there are scenarios where it is crucial to have only a single instance of a class throughout the application. The Singleton design pattern provides a solution by restricting the instantiation of a class to a single object. This pattern guarantees that there is only one instance of the class, providing global access to that instance. In this article, we will explore the fundamental principles, advantages, and potential disadvantages of the Singleton design pattern, emphasizing its significance in managing single instances and facilitating global access to objects.
The Singleton design pattern is a creational pattern that ensures the existence of only one instance of a class. It achieves this by providing a global access point to that instance and by restricting the creation of new instances. The pattern typically involves the following components:
- Private Constructor: The class has a private constructor to prevent direct instantiation from external sources.
- Private Static Instance: The class holds a private static instance of itself.
- Public Static Method: The class provides a public static method that returns the single instance of the class. This method typically creates the instance if it doesn’t exist, or simply returns the existing instance.
Why Singleton Design Pattern in Java?
We need to use the Singleton Design Pattern in Java when we need to ensure that only one instance of a particular class is going to be created and then provide simple global access to that instance for the entire application.
As you can see in the above diagram, different clients (Client A, Client B, and Client C) trying to get the singleton instance. Once the client gets the singleton instance then they can invoke the methods (Method 1, Method 2, and Method n) using the same instance.
Advantages of Singleton Design Pattern in Java
Some of the advantages of using the singleton pattern are:
- Single Instance: The Singleton pattern ensures that only one instance of a class exists throughout the application. This can be crucial in scenarios where having multiple instances could lead to conflicts, resource wastage, or inconsistent behavior.
- Global Access: The Singleton pattern provides a global access point to a single instance. This allows other parts of the application to easily access and use the instance without the need for explicit object passing or dependency injection.
- Lazy Initialization: The Singleton pattern supports lazy initialization, meaning that the instance is created only when it is first requested. This can be advantageous in terms of memory and resource management, as the instance is not created until it is actually needed.
- Thread Safety: Well-implemented Singleton patterns can ensure thread safety by handling concurrent access to a single instance. This avoids race conditions and maintains consistency in multi-threaded environments.
- Efficient Resource Management: In cases where the Singleton represents a shared resource, such as a database connection or a file system handle, using the Singleton pattern ensures efficient resource management. The single instance can be responsible for managing the resource’s lifecycle and preventing resource leaks or conflicts.
Disadvantages of Singleton Design Pattern in Java
Some of the disadvantages of using the singleton pattern are:
- Global State: The Singleton pattern introduces a global state, as the single instance is accessible globally. This can make it harder to track and reason about the flow of data and dependencies within the application. The global state can lead to coupling between different parts of the system, making it more difficult to modify and test individual components.
- Potential for Overuse: The Singleton pattern can be overused, leading to a proliferation of singletons throughout the application. This can make the codebase more complex, reduce the clarity of object dependencies, and hinder code maintainability. Careful consideration should be given to determining if a particular class truly requires a Singleton.
- Difficulty in Testing and Mocking: The global nature of the Singleton instance can make testing and mocking more challenging. Since the instance is tightly coupled to the application, it can be difficult to replace it with a mock or stub during unit testing. This can make it harder to isolate components and verify their behavior independently.
- Concealed Dependencies: Singleton instances can hide their dependencies, making it harder to reason about their initialization and lifecycle. Dependencies may be hidden within the Singleton class, leading to hidden coupling and potential issues when trying to replace or modify those dependencies.
- Limited Extensibility: The Singleton pattern can limit extensibility. Since the single instance is defined at the class level, it may be challenging to introduce variations or alternative implementations of the Singleton. This can hinder the ability to adapt the system to changing requirements or to substitute different behaviors.
Note: The Singleton design pattern offers valuable advantages in ensuring single instances of classes and providing global access to those instances. Its benefits include the guarantee of a single instance, global access, lazy initialization, thread safety, and efficient resource management. However, it is important to consider potential disadvantages, such as the introduction of a global state, the potential for overuse, difficulties in testing and mocking, concealed dependencies, and limited extensibility. By carefully evaluating the specific needs of the system and considering the trade-offs, developers can leverage the Singleton pattern to manage single instances effectively while being mindful of its potential drawbacks.
Example to Understand Singleton Design Pattern in Java
Let’s consider a real-world example where the Singleton pattern can be applied: a database management system in a software application. The Singleton pattern can be used to ensure that there is only one database connection shared across the entire application.
The database management class can be designed as a Singleton, where the class’s constructor is made private to prevent direct instantiation. The class will provide a static method, such as “getInstance()”, which will be responsible for creating and returning the single instance of the database connection.
By using the Singleton pattern, the application can achieve several benefits. Firstly, it ensures that all parts of the application share the same connection, preventing inconsistencies and providing a unified log output. Secondly, it allows global access to the database connection, enabling any component in the system to use the database. Additionally, the Singleton pattern provides a centralized point for managing and configuring the database connection. This can be described using the following UML:
Setting up MySQL
In this project, we will be connecting the Java application to JDBC. For this, you have first to install MySQL and create a database and table. This is the procedure to do so:
Step 1: Download, install, and set up MySQL. You may use MySQL Workbench or MySQL shell. Here we will use MySQL shell:
Step 2: Create a database using the CREATE DATABASE command. Also, check that the database has been created successfully:
Step 3: Set the newly created designdb database to the current database:
Step 4: Create a table called User. Verify that the table has been created:
Implementing Singleton Design Pattern in Java
Step 1: Create a new directory to store all the class files of this project.
Step 2: Open VS Code and create a new project, called singleton.
Step 3: In the project, create a new file called JDBCSingleton.java. This file will handle all database operations and connections.
Step 4: Import the following packages:
Step 5: In the JDBCSingleton class, add the following fields:
Step 6: In the JDBCSingleton class, write the getInstance() function:
Step 7: In the JDBCSingleton class, write the getConnection() function:
Remember to enter the correct user id and password, as per your MySQL configuration.
Step 8: In the JDBCSingleton class, write the insert() function:
This function takes in the name and email as arguments. It then constructs an SQL statement and executes it. This statement is supposed to enter the values into the database.
Step 9: In the JDBCSingleton class, write the view() function:
This function takes in the name as its argument. It then constructs an SQL statement and executes it. This statement is supposed to display all the values in the database with that particular name.
Step 10: In the JDBCSingleton class, write the update() function:
This function takes in the name and email as its argument. It then constructs an SQL statement and executes it. This statement is supposed to update the email field to the email specified by the user. This is for all fields where the name matches.
Step 11: In the JDBCSingleton class, write the delete() function:
This function takes in the name and email as its argument. It then constructs an SQL statement and executes it. This statement is supposed to delete the record where the id matches.
Step 12: In the project, create a new file called JDBCSingletonDemo.java. This file will contain the main() function.
Step 13: Import the following packages:
Step 14: In the JDBCSingletonDemo class, add the following fields:
Step 15: In the JDBCSingletonDemo class, write the main() function:
In the main() function, we basically loop the menu until the user does not choose to exit the program. As long as the program is running (and the user does not exit), the user will be asked which operation to perform on the database. Based on this, we call the function. These functions have to be written the same file (JDBCSingletonDemo.java).
Step 16: In the JDBCSingletonDemo class, write the insert() function:
This function takes input from the user and calls the insert() function from the JDBCSimpleton object.
Step 17: In the JDBCSingletonDemo class, write the view() function:
This function takes input from the user and calls the view() function from the JDBCSimpleton object.
Step 18: In the JDBCSingletonDemo class, write the update() function:
Step 19: In the JDBCSingletonDemo class, write the delete() function:
Step 20: Compile and execute the application. Ensure compilation is successful.
Step 21: Insert an object into the database:
Verify that the insertion is successful in MySQL:
Step 22: Update the object inserted in Step 20:
Verify that the update is successful in MySQL:
Step 23: Delete the object inserted in Step 20:
Verify that the update is successful in MySQL:
Congratulations! You now know how to code a singleton pattern!
The Complete Example Code
JDBCSingleton.java
import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class JDBCSingleton { private static JDBCSingleton jdbc; private JDBCSingleton(){} public static JDBCSingleton getInstance() { if (jdbc == null) jdbc = new JDBCSingleton(); return jdbc; } private static Connection getConnection() throws ClassNotFoundException, SQLException { Class.forName("com.mysql.jdbc.Driver"); return DriverManager.getConnection("jdbc:mysql://localhost:3306/designdb", "root", "DotNet23"); } public int insert (String name, String email) throws SQLException { Connection c = null; PreparedStatement p = null; int record = 0; try { c = this.getConnection(); p = c.prepareStatement("INSERT INTO User(name,email) VALUES (\"?\",\"?\")"); p.setString(1, name); p.setString(2, email); record = p.executeUpdate(); } catch (Exception e) { System.out.println(e.getMessage()); } finally { if (p != null) p.close(); if (c != null) c.close(); } return record; } public void view (String name) throws SQLException { Connection c = null; PreparedStatement p = null; ResultSet r = null; try { c = this.getConnection(); p = c.prepareStatement("SELECT * FROM User WHERE name = ?"); p.setString(1, name); r = p.executeQuery(); while (r.next()) System.out.println("Name: " + r.getString(2) + " Email: " + r.getString(3)); } catch (Exception e) { System.out.println(e.getMessage()); } finally { if (r != null) r.close(); if (p != null) p.close(); if (c != null) c.close(); } } public int update (String name, String email) throws SQLException { Connection c = null; PreparedStatement p = null; int record = 0; try { c = this.getConnection(); p = c.prepareStatement("UPDATE User SET email = ? WHERE name = ?"); p.setString(1, email); p.setString(2, name); record = p.executeUpdate(); } catch (Exception e) { System.out.println(e.getMessage()); } finally { if (p != null) p.close(); if (c != null) c.close(); } return record; } public int delete (int id) throws SQLException { Connection c = null; PreparedStatement p = null; int record = 0; try { c = this.getConnection(); p = c.prepareStatement("DELETE FROM User WHERE id = ?"); p.setString(1, Integer.toString(id)); record = p.executeUpdate(); } catch (Exception e) { System.out.println(e.getMessage()); } finally { if (p != null) p.close(); if (c != null) c.close(); } return record; } }
JDBCSingletonDemo.java
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; public class JDBCSingletonDemo { static BufferedReader br; static JDBCSingleton jdbc; public static void main(String[] args) throws IOException { br = new BufferedReader(new InputStreamReader(System.in)); jdbc = JDBCSingleton.getInstance(); int choice; do { System.out.println("Select your choice: "); System.out.println("1. Insert into database"); System.out.println("2. View database contents"); System.out.println("3. Update database"); System.out.println("4. Delete from database"); System.out.println("0. Exit Application"); System.out.println(); System.out.print("Enter your choice here: "); choice = Integer.parseInt(br.readLine()); switch(choice) { case 1: insert(); break; case 2: view(); break; case 3: update(); break; case 4: delete(); break; default: return; } } while (choice != 0); } private static void insert() throws IOException { System.out.println(); System.out.println("Inserting into database:"); System.out.println(); System.out.print("Enter name: "); String name = br.readLine(); System.out.print("Enter email: "); String email = br.readLine(); try { int i = jdbc.insert(name, email); if (i > 0) System.out.println("Data insertion successful!"); else System.out.println("Data insertion successful!"); } catch (Exception e) { System.out.println(e.getMessage()); } } private static void view() throws IOException { System.out.println(); System.out.println("View database contents:"); System.out.println(); System.out.print("Enter name: "); String name = br.readLine(); try { jdbc.view(name); } catch (Exception e) { System.out.println(e.getMessage()); } } private static void update() throws IOException { System.out.println(); System.out.println("Updating database:"); System.out.println(); System.out.print("Enter name: "); String name = br.readLine(); System.out.print("Enter email: "); String email = br.readLine(); try { int i = jdbc.update(name, email); if (i > 0) System.out.println("Data update successful!"); else System.out.println("Data update successful!"); } catch (Exception e) { System.out.println(e.getMessage()); } } private static void delete() throws IOException { System.out.println(); System.out.println("Updating database:"); System.out.println(); System.out.print("Enter id: "); int id = Integer.parseInt(br.readLine()); try { int i = jdbc.delete(id); if (i > 0) System.out.println("Deletion successful!"); else System.out.println("Deletion successful!"); } catch (Exception e) { System.out.println(e.getMessage()); } } }
UML Diagram of Singleton Design Pattern
The classes can be described as follows:
- SingletonClass: This is the singleton class that handles all the connections and resources.
- DriverClass: This is the class that contains the main() function. This function is responsible for handling the simulation of the program.
Real-Time Scenarios where we use the Singleton Design Pattern in Java:
- Service Proxies: As we know invoking a Service API is an expensive operation in an application. The process that takes most of the time is creating the Service client in order to invoke the service API. If you create the Service proxy as Singleton then it will improve the performance of your application.
- Facades: You can also create Database connections as Singleton which can improve the performance of the application.
- Logs: In an application, performing the I/O operation on a file is an expensive operation. If you create your Logger as Singleton then it will improve the performance of the I/O operation.
- Data Sharing: If you have any constant values or configuration values then you can keep these values in Singleton so that these can be read by other components of the application.
- Caching: As we know fetching data from a database is a time-consuming process. In your application, you can cache the master and configuration in memory which will avoid the DB calls. In such situations, the Singleton class can be used to handle the caching with thread synchronization in an efficient manner which drastically improves the performance of the application.
In the next article, I am going to discuss Prototype Design Patterns in Java with Examples. Here, in this article, I try to explain Singleton Design Pattern in Java with Real-Time Examples. I hope you enjoy this Singleton Design Pattern in Java article.