As stated in this article, singleton is not thread-safe by default. Let’s see how to make it thread-safe.
Basic singleton class
First, let’s recap how to create a basic singleton:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| public class Singleton {
private static Singleton instance;
private Singleton() {
// initialization code
}
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
|
This implementation does not guarantee correct behavior in a multithreaded environment.
How to make a singleton thread-safe
We can achieve singleton thread-safety in several ways:
- Eager initialization
- Lazy initialization with method synchronization
- Double-checked initialization with synchronized block and volatile variable
Eager initialization
In this approach, the instance is created when the class itself gets loaded in the application context to ensure thread-safety without relying on any synchronization mechanism.
However, it could lead to a waste of resources if the instance is never used, and it doesn’t allow passing parameters during instantiation.
1
2
3
4
5
6
7
8
9
10
11
| public class EagerSingleton {
// Instance created at the time of class loading
private static final EagerSingleton instance = new EagerSingleton();
private EagerSingleton() {}
public static EagerSingleton getInstance() {
return instance;
}
}
|
Lazy initialization with method synchronization
This approach ensures thread safety by synchronizing the getInstance()
method. However, this method can suffer from performance issues due to synchronization overhead on every call to getInstance()
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| public class SynchronizedSingleton {
private static SynchronizedSingleton instance;
private SynchronizedSingleton() {}
// Synchronized method to control simultaneous access
public static synchronized SynchronizedSingleton getInstance() {
if (instance == null) {
instance = new SynchronizedSingleton();
}
return instance;
}
}
|
Double-checked initialization with synchronized block and volatile variable
In this approach, similar the previous one, the synchronization overhead is reduced by synchronizing only the first time the instance is accessed.
The instance variable is marked as volatile
to prevent the occurrence of partially constructed instances.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| public class DoubleCheckedSingleton {
// volatile keyword ensures that multiple threads handle the instance variable correctly
private static volatile DoubleCheckedSingleton instance;
private DoubleCheckedSingleton() {}
// Method to return the singleton instance with double-checked locking
public static DoubleCheckedSingleton getInstance() {
if (instance == null) { // First check without synchronization
synchronized (DoubleCheckedSingleton.class) {
if (instance == null) { // Second check with synchronization
instance = new DoubleCheckedSingleton();
}
}
}
return instance;
}
}
|