The traditional way of implementing Singleton design pattern is given in Listing 1. The implementing class provides a static method which instantiates it if no instance already exist. This method is synchronized to ensure that not more than one thread can concurrently execute it and thereby create more than one instance of the class.
public class DBConnectionFactory {
private static DBConnectionFactory instance;
private DBConnectionFactory() {
...
}
public static synchronized DBConnectionFactory getInstance() {
if(instance == null) {
instance = new DBConnectionFactory();
}
return instance;
}
}
Listing 1. Traditional way of writing a Singleton class
In the above code, once a thread starts executing getInstance()
it obtains a class level lock, blocking other threads from accessing the class. In this way, each thread while trying to get the singleton instance is blocking the other threads. Is this the intent of declaring getInstance() synchronized
? No. The getInstance()
method is synchronized
to ensure that not more than one thread creates an instance of the class concurrently or in other words not more than one instances of this class are created in one JVM even if it is accessed by multiple threads concurrently. So, why should the getInstance()
block threads from concurrently accessing the singleton instance?
A lock-free and faster approach of implementing Singleton design pattern is given below in Listing 2. The getInstance()
in this implementation is not synchronized
and it does not block other threads from concurrently accessing the singleton instance. The other threads are blocked only while trying to instantiate this class.
public class DBConnectionFactory {
private static volatile DBConnectionFactory instance;
private DBConnectionFactory() {
...
}
public static DBConnectionFactory getInstance() {
if(instance == null) {
synchronized(DBConnectionFactory.class) {
if(instance == null) {
instance = new DBConnectionFactory();
}
}
}
return instance;
}
}
Listing 2. Lock-free and faster Singleton implementation
The double-checking of whether instance is null
in Listing 2 is to ensure that only one instance of this class is created. It may happen that more than one threads concurrently evaluate the instance as null. So when any one of these threads obtain the class level lock by executing the synchronized
block, it should be ensured again that no instance already exists before creating one.
The singleton instance is declared volatile to ensure that as soon as the instance is created by a thread within the synchronized block, it is immediately visible to other threads, even before the instantiating thread leaves the synchronized block.
While writing thread-safe programs, the synchronized
blocks are preferred over synchronized
methods for locking. The synchronized
block should lock only the critical section of the code instead of locking the entire method. This approach reduces thread contention.