In this article, we will discuss how to create or construct a singleton class in a multi-threaded environment
This is one of the top interview questions for experienced Java developers. We will list down series of questions before delving into details,
- Do you know Singleton design pattern?
- Write code for Singleton design pattern?
- But this is poorly written code, can you write some improved or performance oriented code ?
- How will you handle singleton design pattern in a multi-threaded environment?
- Explain Double-Checked Locking pattern?
Singleton design pattern:
Singleton design pattern is the
- solution proposed to return same instance every time
- restrict instantiation of a class more than once
- exactly one copy is available at any given point of time
- ensures only one instance is available in a Java Virtual Machine
Q) How to check, whether 2 instances are same or different ?
- Answer is always check hash code of the returned instance
- If it is Same, then both instances are same and it is singleton
- If it is Different, then both are different instances and there is something wrong with program logic
1. Singleton design pattern with Eager Instantiation :
Basic steps to create Singleton class
- Step 1: private static variable of the INSTANCE of the same class (this is only time instance of this class get created)
- Step 2: Provide private constructor to restrict instatiation from outside class
- Step 3: Provide public static getInstance() method returning same INSTANCE every time
- Note: These are steps for eager initialization
SingletonDesignPatternWithEagerInitialization.java
package in.bench.resources.singleton.design.pattern;
public class SingletonDesignPatternWithEagerInitialization {
// Step 1: private static variable of INSTANCE variable
private static SingletonDesignPatternWithEagerInitialization
INSTANCE = new SingletonDesignPatternWithEagerInitialization();
// Step 2: private constructor
private SingletonDesignPatternWithEagerInitialization() {
}
// Step 3: Provide public static getInstance() method
// returning same INSTANCE same time
public static SingletonDesignPatternWithEagerInitialization
getInstance() {
return INSTANCE;
}
}
1.1 Issues with above approach :
- The above written code is very poor in terms of performance
- because program returns singleton instance eagerly
- i.e.; it instantiates and keep instance ready to be available
- even before asking to return
2. Lazy Instantiation :
We can actually write more improved version of above code with Lazy initialization
Basic steps to create Singleton class using Lazy Initialization
- Step 1: Just declare private static variable of the same class (beware don’t instantiate)
- Step 2: Provide private constructor to restrict instatiation from outside class
- Step 3: Provide public static getInstance() method and check
- Step 3.a: If INSTANCE variable is null, then only instantiate
- Step 3.b: Otherwise, return already instantiated INSTANCE variable
- Note: These are steps for lazy initialization
Let us move on,
SingletonDesignPatternWithLazyInitialization.java
package in.bench.resources.singleton.design.pattern;
public class SingletonDesignPatternWithLazyInitialization {
// Step 1: private static variable of INSTANCE variable
private static SingletonDesignPatternWithLazyInitialization
INSTANCE;
// Step 2: private constructor
private SingletonDesignPatternWithLazyInitialization() {
}
// Step 3: Provide public static getInstance() method
// returning INSTANCE after checking
public static SingletonDesignPatternWithLazyInitialization
getInstance() {
if(null == INSTANCE){
INSTANCE = new
SingletonDesignPatternWithLazyInitialization();
}
return INSTANCE;
}
}
3. Singleton design pattern in a multi-threaded environment
3.1.1 Issues with Lazy-Initialization approach :
- Although, we have done performance optimization for singleton design pattern with lazy initialization but still there are certain issues with above approach
- So, before we start coding singleton class in a multi-threaded environment, first we should understand problem with lazy initialization
- In the above example for Lazy Initialization, suppose 2 or more threads execute in parallel or concurrent, then there may be a issue with multiple instances being instantiated as explained in the below step
3.1.2 Let us understand in steps:
- Thread-1 got the chance and it is put into execution
- It finds the INSTANCE to be null and therefore Thread-1 instantiates
- Concurrently, if any other thread got chance and if it tries to executes, then there may be a possibility of new instance is getting created, although it is 50 % chance
- Because, new Thread-1 haven’t completed with creation of singleton INSTANCE and another thread at the same time finds singleton INSTANCE to be null and tries to creates another one
To overcome this situation, we need to execute lazy instance creation inside synchronized block
3.2 Solution for lazy initialization :
Basic steps to create Singleton class using Lazy Initialization in a Multi-threaded environment
- Step 1: Just declare private static variable of the same class (beware don’t instantiate)
- Step 2: Provide private constructor to restrict instantiation from outside class
- Step 3: Provide public static getInstance() method and check
- Step 3.a: If INSTANCE variable is null, then only instantiate
- Step 3.b: Otherwise, return already instantiated INSTANCE variable
- Synchronized: Put both above checks inside synchronized block
- Step 4: In addition to above detailed steps, also make INSTANCE variable as volatile. This will help getting latest updated copy every time, as it will read from main memory than in its own CPU-cache area
- Note: If your singleton INSTANCE is going to be executed in a single threaded environment, then there is no need of making INSTANCE variable as volatile
SingletonDesignPatternInMultiThreadedEnvironment.java
package in.bench.resources.singleton.design.pattern;
public class SingletonDesignPatternInMultiThreadedEnvironment {
// Step 1: private static variable of INSTANCE variable
private static volatile
SingletonDesignPatternInMultiThreadedEnvironment INSTANCE;
// Step 2: private constructor
private SingletonDesignPatternInMultiThreadedEnvironment() {
}
// Step 3: Provide public static getInstance() method
// returning INSTANCE after checking
public static SingletonDesignPatternInMultiThreadedEnvironment
getInstance() {
// synchronized block
synchronized
(SingletonDesignPatternInMultiThreadedEnvironment.class){
if(null == INSTANCE){
INSTANCE =
new
SingletonDesignPatternInMultiThreadedEnvironment();
}
return INSTANCE;
}
}
}
This way, we can assure that every time single & same instance is returned
4. Double-checked locking – DCL
4.1 Performance issue with above approach:
But again, there is a performance issue with above program. Let us understand,
- Assume that ONE instance is created and available for use (i.e.; singleton instance)
- And with this single & same instance, some thread (Thread-Arya) is executing in a multi-threaded environment
- Now suppose a new thread (Thread-Surya) got execution cycle and trying to get singleton instance, although it is already created & available for use
- But Thread-Surya has to wait till Thread-Arya releases lock or comes out of synchronized block
- This is bad and poor situation, reason being singleton instance is already created and still it has to wait to get that instance
- Ideally speaking, Thread-Surya doesn’t need to wait for Thread-Arya to release lock and then check condition and then proceed with its execution
4.2 Design singleton pattern in a such a way that
- Once, if singleton instance is created & available for use
- Then no threads needs to wait
- Rather, it should to get singleton instance and continue with its execution
To design such pattern, we need to look into double-checked locking design pattern
Let us move on and see exactly Double-Checked Locking Pattern in details
SingletonDesignPatternWithDCL.java
package in.bench.resources.singleton.design.pattern;
public class SingletonDesignPatternWithDCL {
// Step 1: private static variable of INSTANCE variable
private static volatile SingletonDesignPatternWithDCL
INSTANCE;
// Step 2: private constructor
private SingletonDesignPatternWithDCL() {
}
// Step 3: Provide public static getInstance() method
// returning INSTANCE after checking
public static SingletonDesignPatternWithDCL getInstance() {
// double-checking lock
if(null == INSTANCE){
// synchronized block
synchronized (SingletonDesignPatternWithDCL.class) {
if(null == INSTANCE){
INSTANCE = new SingletonDesignPatternWithDCL();
}
}
}
return INSTANCE;
}
}
5. Conclusion:
We have covered almost for every possible situation that may arise performance issues while dealing with Singleton design pattern, like
- Eager initialization
- Lazy initialization
- Lazy initializtion in a multi-threaded environment
- Double checking lock desing pattern
It is a design choice to choose from above listed approaches to begin with, as starting with double-checking lock in a non-threaded environment yields poor results
So, it is always design choice to look that which approach fits our case perfectly for our business requirement
Related Articles:
- Java – Serialization and De-Serialization Tutorial Index
- Java – Introduction to Serialization and De-Serialization
- Java – Serializable interface
- Java – Transient keyword with Serialization
- Java – Transient keyword with static variable in Serialization
- Java – Transient keyword with final variable in Serialization
- Java – Serializing a variable with transient modifier or keyword
- Java – Order of Serialization and De-Serialization
- Java – Serialization with Aggregation
- Java – Serialization with Inheritance
- Java – Externalization in detail
- Java – Serializable v/s Externalizable
- Java – Importance of SerialVersionUID in Serialization
- Java – Singleton Design pattern with Serialization
- Java – How to construct a singleton class in a multi-threaded environment ?
- Java – Singleton design pattern, restricting Object creation by overriding readResolve() method
- Java – How to stop Serialization ?
- Java – How to serialize and de-serialize ArrayList ?
- Java – Interview question & answers on Serialization and Externalization
References:
- https://www.benchresources.net/singleton-design-pattern-with-java-serialization/
- https://blogs.oracle.com/JavaFundamentals/entry/using_enhanced_for_loops_with
- http://www.oracle.com/technetwork/articles/java/singleton-1577166.html
- http://www.javaworld.com/article/2074979/java-concurrency/double-checked-locking–clever–but-broken.html
Happy Coding !!
Happy Learning !!