41 Superman Problem
41 Superman Problem
41 Superman Problem
Problem
You are designing a library of superheroes for a video game that your
fellow developers will consume. Your library should always create a
single instance of any of the superheroes and return the same instance to
all the requesting consumers.
Say, you start with the class Superman . Your task is to make sure that other
developers using your class can never instantiate multiple copies of
superman. After all, there is only one superman!
Solution
You probably guessed we are going to use the singleton pattern to solve
this problem. The singleton pattern sounds very naive and simple but
when it comes to implementing it correctly in Java, it's no cakewalk.
First let us understand what the pattern is. A singleton pattern allows
only a single object/instance of a class to ever exist during an
application run.
// Object method
public void fly() {
System.out.println("I am Superman & I can fly !");
}
}
Here's what your interviewer will tell you when you write this code:
What if the no one likes Superman and instead creates Batman in the
game. You just created Superman and he kept waiting without ever
being called upon to save the world. It's a waste of Superman's time
and also the memory and other resources he'll consume.
The next version is what most candidates would write and is incorrect.
private SupermanWithFlaws() {
// Object method
public void fly() {
System.out.println("I am Superman & I can fly !");
}
}
As any reader of this course should realize by now (if I have done a good
job of teaching) that the getInstance() method would fail miserably in a
multi-threaded scenario. A thread can context switch out just before it
initializes the Superman, causing later threads to also fall into the if
clause and end up creating multiple superman objects.
The naive way to fix this issue is to use our good friend synchronized and
either add synchronized to the signature of the getInstance() method or
add a synchronized block within the method body. Thee mutual exclusion
ensures that only one thread gets to initialize the object.
private SupermanCorrectButSlow() {
// Object method
public void fly() {
System.out.println("I am Superman & I can fly !");
}
}
private SupermanSlightlyBetter() {
return superman;
}
}
The above solution seems almost correct. In fact, it'll appear correct
unless you understand how the intricacies of Java's memory model and
compiler optimizations can affect thread behaviors. The memory model
defines what state a thread may see when it reads a memory location
modified by other threads. The above solution needs one last missing
piece but before we add that consider the below scenario:
1. Thread A comes along and gets to the second if check and allocates
memory for the superman object but doesn't complete construction
of the object and gets switched out. The Java memory model doesn't
ensure that the constructor completes before the reference to the
new object is assigned to an instance. It is possible that the variable
superman is non-null but the object it points to, is still being
initialized in the constructor by another thread.
2. Thread B wants to use the superman object and since the memory is
already allocated for the object it fails the first if check and returns a
semi-constructed superman object. Attempt to use a partially created
object results in a crash or undefined behavior.
To fix the above issue, we mark our superman static object as volatile .
The happens-before semantics of volatile guarantee that the faulty
scenario of threads A and B never happens.
Last but not the least, double-checked locking (DCL) is an antipattern and
its utility has dwindled over time as the JVM startup and uncontended
synchronization speeds have improved.
private Superman() {
if (superman == null) {
synchronized (Superman.class) {
if (superman == null) {
superman = new Superman();
}
}
}
return superman;
}