Singleton¶
Singleton is a design pattern that describes how to create an object that can be constructed in a given application at most once.
Types¶
Singletons can be divided into two main groups:
lazy
- an object instance is only created when the application wants to use it for the first time.eager
- an object instance is created on startup of the application.
There are two ways to create an eager singleton in Java:
eager
with the usage of classeager
with the usage of enum.
Both of the above types are suitable for multi-threaded applications.
There are also two ways to create a lazy singleton in Java:
lazy
, which is not suitable for use in multi-threaded applications.lazy double checked
, which can be used in multi-threaded applications.
That gives us a total of four ways to create singletons.
Pros and cons¶
The singleton is a controversial pattern because it breaks the Single Responsibility rule. Beyond that, the same instance is used globally in the application, it can create many dependencies between objects. Finally, the code with a singleton may become unreadable and difficult to fix in the case of application errors. However, the singleton also has its advantages. It can save the memory used by the application, and time if creating the object is time-consuming.
Despite the disadvantages, the singleton is a very important and often used pattern. It is often used in applications based on the Spring framework.
Construction¶
Because the singleton requires one class instance , hence we must:
- block the possibility of creating an object by using the operator
new
, i.e. we must declare the constructor as private. - the field of the object in which the singleton will be stored should be "detached" from the class instance, i.e. the reference to the singleton will be kept in the static field.
- we have to add the possibility to access such an instance (or create it for a
lazy
singleton). Because the singleton field is static, the method that gets the reference value should be static too.
Eager singletons¶
Class-based singleton¶
In order to create an eager
singleton based on a class, we must remember all construction points. The example below shows how to define such a singleton and then how to use it.
package pl.sdacademy;
public class SimpleCounter {
// static field in which we keep the singleton reference
// it is an eager singleton so we create an instance by assigning it to the field
private static final SimpleCounter INSTANCE = new SimpleCounter();
// getter for singleton reference
public static SimpleCounter getInstance() {
return INSTANCE;
}
// hidden constructor
private SimpleCounter() {}
private int currentCount = 0;
public int getCurrentCount() {
return currentCount;
}
public void increment() {
currentCount++;
}
}
public class SimpleCounterUsage {
public static void main(String[] args) {
SimpleCounter simpleCounterA = SimpleCounter.getInstance();
SimpleCounter simpleCounterB = SimpleCounter.getInstance();
System.out.println(simpleCounterA == simpleCounterB); // true -> both references point to the same object
simpleCounterA.increment();
simpleCounterB.increment();
System.out.println(simpleCounterA.getCurrentCount()); // 2
}
}
ATTENTION: The singleton reference field and corresponding getter can have any name, but the
instance
name is most often used .
Singleton based on enum¶
We can also use enum
to create a eager
singleton. For this purpose, such an object should have one value available. The value of the enum
object is initialized at the JVM startup and we can be sure that it will happen exactly once. The enum
object can have only private constructors, so we don't need to define it. To get the reference value, we do not need to create an additional getter, because the enum allows access to the value using dot notation. So, the simplest enum-based eager singleton might look like this:
public enum SimpleSingletonExample {
INSTANCE;
}
Previous example using an enum instead of a class would look like this:
public enum SimpleCounter {
INSTANCE;
private int currentCount = 0;
public int getCurrentCount() {
return currentCount;
}
public void increment() {
currentCount++;
}
}
package pl.sdacademy;
public class SimpleCounterUsage {
public static void main(String[] args) {
SimpleCounter simpleCounterA = SimpleCounter.INSTANCE;
SimpleCounter simpleCounterB = SimpleCounter.INSTANCE;
System.out.println(simpleCounterA == simpleCounterB); // also true
simpleCounterA.increment();
simpleCounterB.increment();
System.out.println(simpleCounterA.getCurrentCount()); // 2
}
}
Lazy singletons¶
The lazy singleton, unlike eager singlets, must be created when the reference for such a singleton is taken for the first time, e.g.:
package pl.sdacademy;
import java.util.ArrayList;
import java.util.List;
public class CommonStorage {
private static CommonStorage instance;
public static CommonStorage getInstance() {
if (instance == null) { // (1)
instance = new CommonStorage(); // (2)
}
return instance;
}
private List<Integer> values = new ArrayList<>();
private CommonStorage() {
}
public void addValue(final int value) {
values.add(value);
}
public List<Integer> getValues() {
return values;
}
}
package pl.sdacademy;
public class CommonStorageSampleUsage {
public static void main(String[] args) {
CommonStorage commonStorageA = CommonStorage.getInstance(); // the instance is CREATED at this time
CommonStorage commonStorageB = CommonStorage.getInstance(); // second access to previously created instance
System.out.println(commonStorageA == commonStorageB); // true
commonStorageA.addValue(1);
commonStorageB.addValue(2);
System.out.println(commonStorageA.getValues().size()); // list size is 2
}
}
In the example above, consider the lines of code labeled as (1)
and (2)
.. Suppose two threads (let's call them threads A and B) at the same time called the getInstance()
method on the CommonStorage
singleton.
The following situation is possible:
- thread A executes the
(1)
line, enters the if block, but does not yet execute the(2)
code line, i.e. theinstance
field is stillnull
. - thread B executes the line
(1)
, enters the if block - thread A executes the
(2)
line, creates the first instance of the singleton - thread B executes line
(2)
and creates a second instance of the singleton, which is contrary to the assumptions of this design pattern.
The problem described above can be eliminated by using the so-called double checked
singleton, which uses a synchronized block. Besides, this singleton checks twice that the instance
field is equal to null
- the first time without the synchronized
block, the second time in the synchronized
block. The purpose of double checking is to eliminate the need to create an expensive synchronized
block when the instance has already been initialized. The definition of such a singleton might look like this:
package pl.sdacademy;
import java.util.ArrayList;
import java.util.List;
public class CommonStorage {
private static CommonStorage instance;
public static CommonStorage getInstance() {
if (instance == null) { // (1)
synchronized (CommonStorage.class) {
if (instance == null) { // (2)
instance = new CommonStorage();
}
}
}
return instance;
}
private List<Integer> values = new ArrayList<>();
private CommonStorage() {
}
public void addValue(final int value) {
values.add(value);
}
public List<Integer> getValues() {
return values;
}
}
In the above example, if two threads simultaneously call the getInstance
method, we guarantee that the reference will be initialized exactly once.