What is a singleton?
A singleton is one of the simplest design patterns that can be applied to a class. People sometimes say “this class is a singleton,” meaning that this class implements the singleton design pattern. Sometimes it is necessary to write a class for which only one object can be created. For example, a class responsible for logging or connecting to a database. The Singleton design pattern describes how we can accomplish such a task. A singleton is a design pattern that does two things:-
Provides a guarantee that a class will have only one instance of the class.
-
Provides a global access point to an instance of this class.
-
Private constructor. Restricts the ability to create class objects outside of the class itself.
-
A public static method that returns an instance of the class. This method is called
getInstance
. This is the global access point to the class instance.
Implementation options
The singleton design pattern is used in different ways. Each option is good and bad in its own way. Here, as always: there is no ideal, but you need to strive for it. But first of all, let's define what is good and what is bad, and what metrics influence the evaluation of the implementation of a design pattern. Let's start with the positive. Here are the criteria that give the implementation juiciness and attractiveness:-
Lazy initialization: when a class is loaded while the application is running exactly when it is needed.
-
Simplicity and transparency of the code: the metric, of course, is subjective, but important.
-
Thread safety: works correctly in a multi-threaded environment.
-
High performance in a multi-threaded environment: threads block each other minimally or not at all when sharing a resource.
-
Non-lazy initialization: when a class is loaded when the application starts, regardless of whether it is needed or not (a paradox, in the IT world it is better to be lazy)
-
Complexity and poor readability of the code. The metric is also subjective. We will assume that if blood comes from the eyes, the implementation is so-so.
-
Lack of thread safety. In other words, “thread hazard”. Incorrect operation in a multi-threaded environment.
-
Poor performance in a multi-threaded environment: threads block each other all the time or often when sharing a resource.
Code
Now we are ready to consider various implementation options, listing the pros and cons:Simple Solution
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return INSTANCE;
}
}
The simplest implementation. Pros:
-
Simplicity and transparency of the code
-
Thread safety
-
High performance in a multi-threaded environment
- Not lazy initialization.
Lazy Initialization
public class Singleton {
private static Singleton INSTANCE;
private Singleton() {}
public static Singleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
Pros:
-
Lazy initialization.
-
Not thread safe
Synchronized Accessor
public class Singleton {
private static Singleton INSTANCE;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
Pros:
-
Lazy initialization.
-
Thread safety
-
Poor performance in a multi-threaded environment
getInstance
is synchronized, and you can only enter it one at a time. In fact, we do not need to synchronize the entire method, but only that part of it in which we initialize a new class object. But we can't simply wrap synchronized
the part responsible for creating a new object in a block: this will not provide thread safety. It's a little more complicated. The correct synchronization method is given below:
Double Checked Locking
public class Singleton {
private static Singleton INSTANCE;
private Singleton() {
}
public static Singleton getInstance() {
if (INSTANCE == null) {
synchronized (Singleton.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
Pros:
-
Lazy initialization.
-
Thread safety
-
High performance in a multi-threaded environment
-
Not supported on Java versions lower than 1.5 (the volatile keyword was fixed in version 1.5)
INSTANCE
must be either final
, or volatile
. The last implementation we'll discuss today is Class Holder Singleton
.
Class Holder Singleton
public class Singleton {
private Singleton() {
}
private static class SingletonHolder {
public static final Singleton HOLDER_INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.HOLDER_INSTANCE;
}
}
Pros:
-
Lazy initialization.
-
Thread safety.
-
High performance in a multi-threaded environment.
-
For correct operation, it is necessary to guarantee that the class object
Singleton
is initialized without errors. Otherwise, the first method callgetInstance
will end in an errorExceptionInInitializerError
, and all subsequent ones will failNoClassDefFoundError
.
Implementation | Lazy initialization | Thread safety | Multithreading speed | When to use? |
---|---|---|---|---|
Simple Solution | - | + | Fast | Never. Or when lazy initialization is not important. But never better. |
Lazy Initialization | + | - | Not applicable | Always when multithreading is not needed |
Synchronized Accessor | + | + | Slowly | Never. Or when the speed of work with multithreading does not matter. But never better |
Double Checked Locking | + | + | Fast | In rare cases when you need to handle exceptions when creating a singleton. (when Class Holder Singleton is not applicable) |
Class Holder Singleton | + | + | Fast | Always when multithreading is needed and there is a guarantee that a singleton class object will be created without problems. |
Pros and cons of the Singleton pattern
In general, the singleton does exactly what is expected of it:-
Provides a guarantee that a class will have only one instance of the class.
-
Provides a global access point to an instance of this class.
-
Singleton violates the SRP (Single Responsibility Principle) - the Singleton class, in addition to its immediate responsibilities, also controls the number of its copies.
-
The dependency of a regular class or method on a singleton is not visible in the class's public contract.
-
Global variables are bad. The singleton eventually turns into one hefty global variable.
-
The presence of a singleton reduces the testability of the application in general and the classes that use the singleton in particular.
GO TO FULL VERSION