
What is a singleton in Java?
Singleton is one of the simplest class-level design patterns. Sometimes people say "this class is singleton”, which means that the class implements the singleton design pattern. Sometimes it is necessary to write a class where we restrict instantiation to a single object. For example, a class responsible for logging or connecting to a database. The singleton design pattern describes how we can achieve this. A singleton is a design pattern that does two things:It guarantees that there will only ever be one instance of the class.
It provides a single point of global access to that instance.
A private constructor. This limits the ability to create objects of the class outside of the class itself.
A public static method that returns the instance of the class. This method is called getInstance. This is the point of global access to the class instance.
Implementation options
The singleton design pattern is applied in various ways. Each option is good and bad in its own way. As always, there is no perfect option here, but we should strive for one. First of all, let's decide what constitutes good and bad, and what metrics affect how we assess the various implementations of the design pattern. Let's start with the good. Here are factors that make an implementation more juicy and appealing:Lazy initialization: the instance is not created until it is needed.
Simple and transparent code: this metric, of course, is subjective, but it is important.
Thread safety: correct operation in a multi-threaded environment.
High performance in a multi-threaded environment: little or no thread blocking when sharing a resource.
No lazy initialization: when the class is loaded when the application starts, regardless of whether or not it is needed (paradoxically, in the IT world it is better to be lazy)
Complex and difficult-to-read code. This metric is also subjective. If your eyes start bleeding, we'll assume the implementation isn't the best.
Lack of thread safety. In other words, "thread danger". 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're ready to consider various implementation options and indicate the pros and cons:Simple
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return INSTANCE;
}
}
The simplest implementation.
Pros:
Simple and transparent code
Thread safety
High performance in a multi-threaded environment
- No lazy initialization.
Lazy initialization
public class Singleton {
private static final Singleton INSTANCE;
private Singleton() {}
public static Singleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
Pros:
Lazy initialization.
Not thread safe
Synchronized access
public class Singleton {
private static final Singleton INSTANCE;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
Pros:
Lazy initialization.
Thread safety
Poor multithreaded performance
Double-checked locking
public class Singleton {
private static final 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 in earlier versions of Java below 1.5 (the usage of volatile keyword is fixed since the 1.5 version)
Class holder
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.
Correct operation requires a guarantee that the singleton object is initialized without errors. Otherwise, the first call to the getInstance method will result in an ExceptionInInitializerError, and all subsequent calls will produce a NoClassDefFoundError.
Implementation | Lazy initialization | Thread safety | Multithreaded performance | When to use? |
---|---|---|---|---|
Simple | - | + | Fast | Never. Or possibly when lazy initialization is not important. But never would be better. |
Lazy initialization | + | - | Not applicable | Always when multithreading is not needed |
Synchronized access | + | + | Slow | Never. Or possibly when multithreaded performance does not matter. But never would be better. |
Double-checked locking | + | + | Fast | In rare cases when you need to handle exceptions when creating the singleton (when the class holder singleton is not applicable) |
Class holder | + | + | Fast | Whenever multithreading is needed and there is a guarantee that the singleton object will be created without problems. |
Pros and cons of the singleton pattern
In general, a singleton does exactly what is expected of it:It guarantees that there will only ever be one instance of the class.
It provides a single point of global access to that instance.
A singleton violates the single responsibility principle: in addition to its direct duties, the singleton class also controls the number of instances.
An ordinary class's dependence on a singleton is not visible in the class's public contract.
Global variables are bad. Ultimately, a singleton turns into a hefty global variable.
The presence of a singleton reduces the testability of the application as a whole and the classes that use the singleton in particular.
GO TO FULL VERSION