I bet you heard of the Singleton Pattern before, and it doesn't surprise me, because this pattern has been built into so many frameworks nowadays, mostly because it's one the simplest to implement. But what is Singleton Pattern and what problem does it solve?
Definition
According to GoF, Singleton Pattern ensures a class has only one instance and provides a global access to it.
Perfect, why would I need a single instance of a class? Suppose that you have a Logger class that records all your application activity. You don't need to instantiate different objects of the same logger in all your classes, you just need one instance and a means to access it. Another example would be to reduce the use of global variables, so instead of using an excessive number of global variables, you could just create an object using the Singleton Pattern.
Benefits
There are many benefits of the application of this pattern:
- There is controlled access to the sole instance of your class.
- It reduces the excessive use of global variables, which is a bad practice.
- It's easy to maintain because it provides a single point of access.
- It can be lazy or eager loading.
- It can be thread-safe.
Application
The Singleton Pattern can be applied in any of the following cases:
- Class instantiation is resource expensive to perform.
- Global variables reduction.
- By design you must have a sole object of a given class.
Implementing The Singleton Pattern:
To apply the Singleton Pattern in a specific class we must do the following:
- Create a private constructor.
- Define a global static object where we'll store our sole instance.
- Define a static method where we can retrieve the object.
- The method will check if the instance is created. If it's not, it will create it.
There are four different ways of implementing the Singleton Pattern, let's take a look at each one of them:
Lazy Initialization of Singleton Pattern
On this variant of the Singleton Pattern, the instance is created until is requested by the getInstance() method. Before that, there is no instance of the class. This is helpful because we don't consume any resources if it's not necessary. Let's see the following example.
public class LoggerSingletonLazy {
//Sole instance
private static LoggerSingletonLazy instance;
//Private constructor
private LoggerSingletonLazy(){}
/**
* Gets the LoggerSingletonEeager instance
*
* @return
*/
public static LoggerSingletonLazy getInstance(){
/*Checks if instance is created, if it's not if*/
if(instance == null){
instance = new LoggerSingletonLazy();
}
return instance;
}
}
To apply the Singleton Pattern on the LoggerSingletonLazy class, we did the following:
- We defined a private constructor so that it cannot be instantiated outside the class itself.
- We defined a static variable where we'll store the sole instance of the LoggerSingletonLazy class.
- We defined a static method where we can retrieve the sole instance of the class. If there is no instance on the first call, the method will create the object and return it.
We can test if the LoggerSingletonLazy class is returning the same object.
public class App {
public static void main(String[] args){
LoggerSingletonLazy firstLog = LoggerSingletonLazy.getInstance();
System.out.println("First Log Instance: " + firstLog.hashCode());
LoggerSingletonLazy secondLog = LoggerSingletonLazy.getInstance();
System.out.println("Second Log Instance: " + secondLog.hashCode());
LoggerSingletonLazy thirdLog = LoggerSingletonLazy.getInstance();
System.out.println("Third Log Instance: " + thirdLog.hashCode());
}
}
We retrieve an instance of the LoggerSingletonLazy class in three different times and save it in three different variables. We check the hash code to validate if the three variables point to the same object.
Let's see the output of this program:
First Log Instance: 7059277765
Second Log Instance: 7059277765
Thord Log Instance: 7059277765
The three hash codes are the same, so all the variables point to the same object. Therefore, the Singleton Pattern is correctly applied to the class.
Eager Initialization of Singleton Pattern
Now, let's suppose we want to create an instance of the object on the application startup, this is known as eager initialization. It can be helpful for the initial configuration of an object. Let's see an example of this.
public class LoggerSingletonEager {
//Sole instance
private static final LoggerSingletonEager INSTANCE;
//Eager initialization
static{
INSTANCE = new LoggerSingletonEager();
}
//Private constructor
private LoggerSingletonEager(String loggerName){}
public static LoggerSingletonEager getInstance(){
return INSTANCE;
}
}
The difference between this implementation and the previous one is that the object is created in the "static" method, which is executed on the application startup. On this implementation, the "getInstance()" method only retrieves and returns the instance created in the static method.
Singleton Pattern - Thread Safe
There is a potential flaw with the previous designs. Let's suppose that two different threads enter the "getInstance()" method at the same time. Both of them will detect that the instance variable is null, so both will instantiate different objects. This means that our getInstance method is not thread-safe.
Let's change it:
public class LoggerSingletonThreadSafe {
//Sole instance
private static LoggerSingletonThreadSafe instance;
//Private constructor
private LoggerSingletonThreadSafe(){}
//Thread safe method
public synchronized static LoggerSingletonThreadSafe getInstance(){
if(instance == null){
instance = new LoggerSingletonThreadSafe();
}
return instance;
}
}
We use the synchronized keyword to make our getInstance method thread-safe. This means if two or more threads try to access the method at the same time, they will have to do it one at a time.
Singleton Pattern - Double Thread Safe
Most of the time the getInstance method will just retrieve the object, so there is no point in synchronizing the whole method. We just need to synchronize the part of the method where the object is created. Let's change it.
public class LoggerSingletonThreadSafeDouble {
//Sole instance
private static LoggerSingletonThreadSafeDouble instance;
//Private constructor
private LoggerSingletonThreadSafeDouble(){}
public static LoggerSingletonThreadSafeDouble getInstance(){
if(instance == null){
synchronized(LoggerSingletonThreadSafeDouble.class){
instance = new LoggerSingletonThreadSafeDouble();
}
}
return instance;
}
}
We only synchronized the part of the code where the object is created. This is helpful on those objects where the getInstance method does different things besides just retrieving the object.
Conclusion:
The Singleton Pattern is handy when an object instantiation is resource expensive to perform or by requirement, you must have a sole instance of a class. No matter what the reason is, remember that Singleton is just a best practice and it doesn't have to be your only solution when creating objects. Just like any pattern, learn first the specific cases where you can apply the pattern and then learn the syntax.
Top comments (0)