The singleton creation pattern is a common programming idiom. When used with multiple threads, some type of synchronization is required. In an effort to create more efficient code, Java programmers created the double-checked locking idiom to use with the singleton creation pattern to limit the amount of synchronization code. However, due to some less common details of the Java memory model, there is no guarantee that this double-checked locking idiom will work.
It fails occasionally, not always. Also, it fails for non-obvious reasons and contains some cryptic details of the Java memory model. These facts will cause the code to fail because double-checked locking is difficult to track. In the remainder of this article, we'll detail the double-checked locking idiom to understand where it fails.
To understand where the double-checked locking idiom originated, one must understand the common singleton creation idiom, as illustrated in Listing 1:
Listing 1. The singleton creation idiom
import java.util.*; class Singleton { private static Singleton instance; private Vector v; private boolean inUse; private Singleton() { v = new Vector(); v.addElement(new Object()); inUse = true; } public static Singleton getInstance() { if (instance == null) //1 instance = new Singleton(); //2 return instance; //3 } }
The design of this class ensures that only one Singleton
object is created. Constructors are declared as private
, getInstance()
methods just create an object. This implementation is suitable for single-threaded programs. However, when multithreading is introduced, getInstance()
methods must be protected by synchronization. If the method is not protected getInstance()
, Singleton
two different instances of the object may be returned. Suppose two threads call getInstance()
methods concurrently and the calls are performed in the following order:
- Thread 1 calls
getInstance()
the method and decidesinstance
to be at //1null
.
- Thread 1 entered
if
the code block, but was preempted by thread 2 while executing the line of code at //2.
- Thread 2 calls the method and decides
getInstance()
at //1 .instance
null
- Thread 2 enters
if
the code block and creates a newSingleton
object and assigns the variable to this new object at //2instance
.
- Thread 2 returns the object reference at //3
Singleton
.
- Thread 2 is preempted by Thread 1.
- Thread 1 starts where it left off, and executes the //2 line of code, which causes another
Singleton
object to be created.
- Thread 1 returns this object at //3.
The result is that getInstance()
the method creates two Singleton
objects when it should have only created one. This problem is corrected by synchronizing getInstance()
methods so that only one thread is allowed to execute code at a time, as shown in Listing 2:
Listing 2. Thread-safe getInstance() method
public static synchronized Singleton getInstance() { if (instance == null) //1 instance = new Singleton(); //2 return instance; //3 }
getInstance()
The code in Listing 2 works fine for multithreaded access methods. However, when analyzing this code, you realize that synchronization is only required the first time the method is called. Since only the first call executes the code at //2, and only this line of code needs to be synchronized, there is no need to use synchronization on subsequent calls. All other calls are used to determine instance
true and false null
, and return it. Multiple threads are able to safely execute all calls concurrently except the first. However, since this method is a method synchronized
, you need to pay the price of synchronization for every call of this method, even if only the first call needs to be synchronized.
To make this approach more efficient, an idiom called double-checked locking was developed. The idea is to avoid the expensive cost of synchronizing all but the first call. The cost of synchronization varies between different JVMs. In the early days, the price was quite high. With the advent of more advanced JVMs, the cost of synchronization has decreased, but synchronized
there is still a performance penalty for entering and exiting methods or blocks. Regardless of advances in JVM technology, programmers never want to waste processing time unnecessarily.
Since only the //2 line in Listing 2 needs to be synchronized, we can just wrap it in a synchronized block, as shown in Listing 3:
Listing 3. getInstance() method
public static Singleton getInstance() { if (instance == null) { synchronized(Singleton.class) { instance = new Singleton(); } } return instance; }
The code in Listing 3 demonstrates the same problem as Listing 1, illustrated with multithreading. When instance
true null
, two threads can enter if
the statement concurrently. Then, one thread enters synchronized
the block to initialize instance
while the other thread is blocked. When the first thread exits synchronized
the block, the waiting thread enters and creates another Singleton
object. Note: When the second thread enters synchronized
the block, it does not check instance
for negation null
.
double check locking
To deal with the problem in Listing 3, we need to do instance
a second check of . This is where the name "double-checked locking" comes from. Listing 4 is the result of applying the double-checked locking idiom to Listing 3 .
Listing 4. Double-checked locking example
public static Singleton getInstance() { if (instance == null) { synchronized(Singleton.class) { //1 if (instance == null) //2 instance = new Singleton(); //3 } } return instance; }
The theory behind double-checked locking is that the second check at //2 makes it impossible (as in Listing 3) to create two different Singleton
objects. Assume the following sequence of events:
- Thread 1 enters
getInstance()
the method.
- Thread 1 enters the block at //1 because
instance
of .null
synchronized
- Thread 1 is preempted by Thread 2.
- Thread 2 enters
getInstance()
the method.
- Since
instance
it is stillnull
, thread 2 tries to acquire the lock at //1. However, thread 2 blocks at //1 because thread 1 holds the lock.
- Thread 2 is preempted by Thread 1.
- Thread 1 executes, and since the instance is still at //2
null
, thread 1 also creates anSingleton
object and assigns its reference toinstance
.
- Thread 1 exits
synchronized
the block andgetInstance()
returns the instance from the method.
- Thread 1 is preempted by Thread 2.
- Thread 2 acquires the lock at //1 and checks
instance
if it isnull
.
- Since
instance
nonull
, the secondSingleton
object is not created, and the object created by thread 1 is returned.
The theory behind double checked locking is perfect. Unfortunately, the reality is quite different. The problem with double-checked locking is that there is no guarantee that it will run smoothly on a uniprocessor or multiprocessor computer.
The failure of double-checked locking is not due to an implementation bug in the JVM, but to the Java platform memory model. The memory model allows for so-called "out-of-order writes", and this is a major reason why these idioms fail.
write out of order
To explain this, the line //3 in Listing 4 above needs to be revisited. This line of code creates an Singleton
object and initializes variables instance
to reference this object. The problem with this line of code is that the variable may become negated Singleton
before the constructor body is executed .instance
null
What? This statement may surprise you, but it is true. Before explaining how this phenomenon occurs, please temporarily accept this fact, let's first examine how double-checked locking is broken. Suppose the code in Listing 4 executes the following sequence of events:
- Thread 1 enters
getInstance()
the method.
- Thread 1 enters the block at //1 because
instance
of .null
synchronized
- Thread 1 advances to //3, but before the constructor executes , negates the instance
null
.
- Thread 1 is preempted by Thread 2.
- Thread 2 checks if the instance is
null
. Because the instance is not null, thread 2instance
returns a reference to a fully constructed but partially initializedSingleton
object.
- Thread 2 is preempted by Thread 1.
- Thread 1 completes the initialization of the object by running
Singleton
the object's constructor and returning a reference to it.
This sequence of events occurs when thread 2 returns an object whose constructor has not yet been executed.
To show this happening, assume instance =new Singleton();
the following pseudocode is executed for the code line: instance =new Singleton();
mem = allocate(); //Allocate memory for Singleton object. instance = mem; //Note that instance is now non-null, but //has not been initialized. ctorSingleton(instance); //Invoke constructor for Singleton passing //instance.
This pseudocode is not only possible, but actually happens with some JIT compilers. The order of execution is reversed, but given the current memory model, this is allowed to happen. This behavior of the JIT compiler makes the problem of double-checked locking nothing more than an academic exercise.
To illustrate this, assume the code in Listing 5. It contains a stripped version of getInstance()
the method. I've removed the "double-checking" to simplify our review of the generated assembly code (Listing 6). We only care about how the JIT compiler compiles instance=new Singleton();
the code. Additionally, I provide a simple constructor to explicitly illustrate how that constructor works in assembly code.
Listing 5. Singleton class to demonstrate out-of-order writes
class Singleton { private static Singleton instance; private boolean inUse; private int val; private Singleton() { inUse = true; val = 5; } public static Singleton getInstance() { if (instance == null) instance = new Singleton(); return instance; } }
getInstance()
Listing 6 contains the assembly code generated by the Sun JDK 1.2.1 JIT compiler for the method body in Listing 5 .
Listing 6. Assembly code generated from the code in Listing 5
;asm code generated for getInstance 054D20B0 mov eax,[049388C8] ;load instance ref 054D20B5 test eax,eax ;test for null 054D20B7 jne 054D20D7 054D20B9 mov eax,14C0988h 054D20BE call 503EF8F0 ;allocate memory 054D20C3 mov [049388C8],eax ;store pointer in ;instance ref. instance ;non-null and ctor ;has not run 054D20C8 mov ecx,dword ptr [eax] 054D20CA mov dword ptr [ecx],1 ;inline ctor - inUse=true; 054D20D0 mov dword ptr [ecx+4],5 ;inline ctor - val=5; 054D20D7 mov ebx,dword ptr ds:[49388C8h] 054D20DD jmp 054D20B0
NOTE: To refer to the lines of assembly code in the following instructions, I will refer to the last two values of the instruction address since they both begin with 054D20
. For example, B5
rep test eax,eax
.
Assembly code is generated by running a testgetInstance()
program that calls methods in an infinite loop . While the program is running, run the Microsoft Visual C++ debugger and attach it to the Java process representing the test program. Then, break out of execution and find the assembly code that represents that infinite loop.
B0
B5
The first two lines of assembly code at and load the instance
reference from the memory location into and check it. This corresponds to the first line of code in the method in Listing 5 . The first time this method is called, the code executes up to . The code at allocates memory for the object from the heap and stores a pointer to that block of memory in it . The next line of code, , takes the pointer in and stores it back in memory at the instance reference. The result is that it is now NOT and references a valid object. However, the constructor for this object has not yet run, which is exactly what breaks double-checked locking. Then, at the line, the pointer is dereferenced and stored into . The and lines represent inlined constructors that store values and stores to objects. If this code is interrupted by another thread after the execution line and before the constructor completes, the double-checked locking will fail.049388C8
eax
null
getInstance()
instance
null
B9
BE
Singleton
eax
C3
eax
049388C8
instance
null
Singleton
C8
instance
ecx
CA
D0
true
5
Singleton
C3
Not all JIT compilers generate the above code. Some generate code such that negates only after the constructor instance
executes null
. Version 1.3 of the IBM SDK for Java technology and Sun JDK 1.3 both generate such code. However, this does not mean that double-checked locking should be used in these instances. There are some other reasons why this idiom fails. Also, you don't always know which JVMs your code will run on, and JIT compilers can always change to generate code that breaks this idiom.
Double-checked locking: acquires two
Given that the current double-checked locking doesn't work, I included another version of the code, shown in Listing 7, to prevent the out-of-order write problem you just saw.
Listing 7. Attempt to solve the out-of-order write problem
public static Singleton getInstance() { if (instance == null) { synchronized(Singleton.class) { //1 Singleton inst = instance; //2 if (inst == null) { synchronized(Singleton.class) { //3 inst = new Singleton(); //4 } instance = inst; //5 } } } return instance; }
Looking at the code in Listing 7, you should realize that things are getting a little ridiculous. Remember that double checked locking was created to avoid synchronization on simple three line getInstance()
methods. The code in Listing 7 becomes unruly. Also, that code doesn't solve the problem. Careful inspection will reveal the reason.
This code tries to avoid the out-of-order write problem. It tries to solve this by introducing local variables inst
and a second block. synchronized
The theory is implemented as follows:
- Thread 1 enters
getInstance()
the method.
- Thread 1 enters the first block at //1 because
instance
of .null
synchronized
- The value that the local variable
inst
getsinstance
, which is at //2null
.
- Because
inst
ofnull
, thread 1 enters the secondsynchronized
block at //3.
- Thread 1 then starts executing the code at //4 while negating
inst
,null
butSingleton
before the constructor of //4 is executed. (This is the out-of-order write problem we just saw.)
- Thread 1 is preempted by Thread 2.
- Thread 2 enters
getInstance()
the method.
- Because
instance
ofnull
, thread 2 tries to enter the firstsynchronized
block at //1. Since thread 1 currently holds the lock, thread 2 is blocked.
- Thread 1 then completes execution at //4.
- Thread 1 then assigns a fully constructed
Singleton
object to the variable at //5instance
and exits bothsynchronized
blocks.
- Thread 1 returns
instance
.
instance
Then execute thread 2 and assign to at //2inst
.
- Thread 2 finds out
instance
that it is notnull
, and returns it.
The key line here is //5. This line should ensure instance
that only null
one complete Singleton
object is constructed for or referenced. The problem occurs when theory and practice run counter to each other.
The code in Listing 7 is invalid due to the definition of the current memory model. The Java Language Specification ( JLS) mandates synchronized
that code within a block cannot be moved out. synchronized
However, it doesn't say that code outside the block cannot be moved synchronized
into the block.
The JIT compiler will see an optimization opportunity here. This optimization removes the code at //4 and //5, combining and generating the code shown in Listing 8.
Listing 8. The optimized code from Listing 7.
public static Singleton getInstance() { if (instance == null) { synchronized(Singleton.class) { //1 Singleton inst = instance; //2 if (inst == null) { synchronized(Singleton.class) { //3 //inst = new Singleton(); //4 instance = new Singleton(); } //instance = inst; //5 } } } return instance; }
If you do this optimization, you will have the same out-of-order write problem we discussed earlier.
How about declaring every variable volatile?
Another idea is to target variables inst
as well as instance
use keywords volatile
. According to the JLS (see Related topics), declared volatile
variables are considered sequentially consistent, ie, not reordered. But trying to volatile
fix the problem of double-checked locking with the following two problems:
- The problem here is not about sequential consistency, but that the code was moved, not reordered.
- Even when sequential consistency is considered, most JVMs do not implement it correctly
volatile
.
The second point deserves further discussion. Assume the code in Listing 9:
Listing 9. Sequential consistency using volatile
class test { private volatile boolean stop = false; private volatile int num = 0; public void foo() { num = 100; //This can happen second stop = true; //This can happen first //... } public void bar() { if (stop) num += num; //num can == 0! } //... }
According to the JLS, since stop
and num
are declared as volatile
, they should be sequentially consistent. This means that if stop
it ever was true
, num
it must have been set to 100
. However, because of sequential consistency features that many JVMs do not implement volatile
, you cannot rely on this behavior. Therefore, if thread 1 calls foo
and thread 2 calls concurrently bar
, thread 1 may set to before num
it is set to . This will cause the thread to see yes while still being set to . There are additional issues with using atomic numbers with 64-bit variables, but these are beyond the scope of this article. See Resources for more information on this topic.100
stop
true
stop
true
num
0
volatile
solution
The bottom line is this: double-checked locking should not be used in any form, because you cannot guarantee that it will work smoothly on any JVM implementation. JSR-133 is about memory model addressing issues, however, the new memory model will not support double-checked locking. Therefore, you have two options:
- Accept the method shown in Listing 2
getInstance()
for synchronization.
- Abandon synchronization and use a
static
field instead.
Option 2 is shown in Listing 10
Listing 10. Singleton implementation using static fields
class Singleton { private Vector v; private boolean inUse; private static Singleton instance = new Singleton(); private Singleton() { v = new Vector(); inUse = true; //... } public static Singleton getInstance() { return instance; } }
The code in Listing 10 does not use synchronization and ensures that static getInstance()
the method is not created until it is called Singleton
. This is a great option if your goal is to eliminate syncing.
String is not immutable
Given the problem of out-of-order writes and references becoming negated before the constructor executes null
, you might consider String
classes. Suppose you have the following code:
private String str; //... str = new String("hello");
String
Classes should be immutable. Still, given the out-of-order write problem we discussed earlier, would that cause a problem here? The answer is yes. Consider two thread accesses String str
. A thread can see str
a reference to an String
object in which the constructor has not yet run. In fact, Listing 11 contains code that shows this happening. Note that this code only fails on the older JVM I tested it with. Both IBM 1.3 and Sun 1.3 JVMs generate unchanged as expected String
.
Listing 11. Example of mutable String
class StringCreator extends Thread { MutableString ms; public StringCreator(MutableString muts) { ms = muts; } public void run() { while(true) ms.str = new String("hello"); //1 } } class StringReader extends Thread { MutableString ms; public StringReader(MutableString muts) { ms = muts; } public void run() { while(true) { if (!(ms.str.equals("hello"))) //2 { System.out.println("String is not immutable!"); break; } } } } class MutableString { public String str; //3 public static void main(String args[]) { MutableString ms = new MutableString(); //4 new StringCreator(ms).start(); //5 new StringReader(ms).start(); //6 } }
This code creates a MutableString
class at //4 that contains a String
reference shared by the two threads at //3. StringCreator
At lines //5 and //6, two objects and are created on two separate threads StringReader
. Pass in a reference to an MutableString
object. StringCreator
The class enters an infinite loop and creates String
the object at //1 with the value "hello". StringReader
Also enters an infinite loop, and checks at //2 String
whether the value of the current object is "hello". If not, StringReader
the thread prints a message and stops. If String
the class is immutable, you should see no output from this program. If an out-of-order write problem occurs, the only way to StringReader
see str
a reference is never String
an object with a value of "hello".
Running this code on an older JVM such as Sun JDK 1.2.1 will cause out-of-order write issues. and thus result in a non-invariant String
.
conclusion
To avoid costly synchronization in singletons, programmers were very clever and invented the double-checked locking idiom. Unfortunately, given the current memory model, this idiom has not yet become widely used, and it is clearly an unsafe programming construct. Work in this area of redefining the fragile memory model is ongoing. Still, even in the newly proposed memory model, double-checked locking is ineffective. The best solution to this problem is to accept synchronization or use one static field
.
References
-
- You can read the original English version of this article on the developerWorks global site .
- In Peter Haggar's book Practical Java Programming Language Guide (Addison-Wesley, 2000), he covers several Java programming topics, including an entire chapter on multithreading issues and programming techniques.
- The Java Language Specification, Second Edition by Bill Joy et al. (Addison-Wesley, 2000) is the definitive technical reference on the Java programming language.
- The Java Virtual Machine Specification, Second Edition (Addison-Wesley, 1999) , by Tim Lindholm and Frank Yellin, is the definitive document on the Java compiler and runtime environment.
- Visit Bill Pugh's Java Memory Model Web site for a wealth of information on this topic.
- To learn more about
volatile
and 64-bit variants, see Peter Haggar's article "Does Java Guarantee Thread Safety?" in the June 2002 issue of Dr. Dobb's Journal .
- JSR-133 deals with revisions to the Java platform's memory model and threading specification.
- Java software consultant Brian Goetz explains when to use synchronization in " Threading made easy: Synchronization is not the enemy " ( developerWorks , July 2001).
- In " Threading made easy: Sometimes unsharing is best " ( developerWorks , October 2001), Brian Goetz introduces it
ThreadLocal
, and offers some tips for exploiting its power.
- In " Threading made easy: Synchronization is not the enemy " ( developerWorks , February 2001), Alex Roetter introduces the Java Thread API, provides an overview of issues associated with multithreading, and provides solutions to common problems.
- Allen Holub proposes major changes and additions to the Java language in " If I Were King: Proposals for solving threading problems in the Java programming language " ( developerWorks , October 2000).
- Find additional Java technology material in the developerWorks Java technology zone .
- You can read the original English version of this article on the developerWorks global site .
The singleton creation pattern is a common programming idiom. When used with multiple threads, some type of synchronization is required. In an effort to create more efficient code, Java programmers created the double-checked locking idiom to use with the singleton creation pattern to limit the amount of synchronization code. However, due to some less common details of the Java memory model, there is no guarantee that this double-checked locking idiom will work.
It fails occasionally, not always. Also, it fails for non-obvious reasons and contains some cryptic details of the Java memory model. These facts will cause the code to fail because double-checked locking is difficult to track. In the remainder of this article, we'll detail the double-checked locking idiom to understand where it fails.
To understand where the double-checked locking idiom originated, one must understand the common singleton creation idiom, as illustrated in Listing 1:
Listing 1. The singleton creation idiom
import java.util.*; class Singleton { private static Singleton instance; private Vector v; private boolean inUse; private Singleton() { v = new Vector(); v.addElement(new Object()); inUse = true; } public static Singleton getInstance() { if (instance == null) //1 instance = new Singleton(); //2 return instance; //3 } }
The design of this class ensures that only one Singleton
object is created. Constructors are declared as private
, getInstance()
methods just create an object. This implementation is suitable for single-threaded programs. However, when multithreading is introduced, getInstance()
methods must be protected by synchronization. If the method is not protected getInstance()
, Singleton
two different instances of the object may be returned. Suppose two threads call getInstance()
methods concurrently and the calls are performed in the following order:
- Thread 1 calls
getInstance()
the method and decidesinstance
to be at //1null
.
- Thread 1 entered
if
the code block, but was preempted by thread 2 while executing the line of code at //2.
- Thread 2 calls the method and decides
getInstance()
at //1 .instance
null
- Thread 2 enters
if
the code block and creates a newSingleton
object and assigns the variable to this new object at //2instance
.
- Thread 2 returns the object reference at //3
Singleton
.
- Thread 2 is preempted by Thread 1.
- Thread 1 starts where it left off, and executes the //2 line of code, which causes another
Singleton
object to be created.
- Thread 1 returns this object at //3.
The result is that getInstance()
the method creates two Singleton
objects when it should have only created one. This problem is corrected by synchronizing getInstance()
methods so that only one thread is allowed to execute code at a time, as shown in Listing 2:
Listing 2. Thread-safe getInstance() method
public static synchronized Singleton getInstance() { if (instance == null) //1 instance = new Singleton(); //2 return instance; //3 }
getInstance()
The code in Listing 2 works fine for multithreaded access methods. However, when analyzing this code, you realize that synchronization is only required the first time the method is called. Since only the first call executes the code at //2, and only this line of code needs to be synchronized, there is no need to use synchronization on subsequent calls. All other calls are used to determine instance
true and false null
, and return it. Multiple threads are able to safely execute all calls concurrently except the first. However, since this method is a method synchronized
, you need to pay the price of synchronization for every call of this method, even if only the first call needs to be synchronized.
To make this approach more efficient, an idiom called double-checked locking was developed. The idea is to avoid the expensive cost of synchronizing all but the first call. The cost of synchronization varies between different JVMs. In the early days, the price was quite high. With the advent of more advanced JVMs, the cost of synchronization has decreased, but synchronized
there is still a performance penalty for entering and exiting methods or blocks. Regardless of advances in JVM technology, programmers never want to waste processing time unnecessarily.
Since only the //2 line in Listing 2 needs to be synchronized, we can just wrap it in a synchronized block, as shown in Listing 3:
Listing 3. getInstance() method
public static Singleton getInstance() { if (instance == null) { synchronized(Singleton.class) { instance = new Singleton(); } } return instance; }
The code in Listing 3 demonstrates the same problem as Listing 1, illustrated with multithreading. When instance
true null
, two threads can enter if
the statement concurrently. Then, one thread enters synchronized
the block to initialize instance
while the other thread is blocked. When the first thread exits synchronized
the block, the waiting thread enters and creates another Singleton
object. Note: When the second thread enters synchronized
the block, it does not check instance
for negation null
.
double check locking
To deal with the problem in Listing 3, we need to do instance
a second check of . This is where the name "double-checked locking" comes from. Listing 4 is the result of applying the double-checked locking idiom to Listing 3 .
Listing 4. Double-checked locking example
public static Singleton getInstance() { if (instance == null) { synchronized(Singleton.class) { //1 if (instance == null) //2 instance = new Singleton(); //3 } } return instance; }
The theory behind double-checked locking is that the second check at //2 makes it impossible (as in Listing 3) to create two different Singleton
objects. Assume the following sequence of events:
- Thread 1 enters
getInstance()
the method.
- Thread 1 enters the block at //1 because
instance
of .null
synchronized
- Thread 1 is preempted by Thread 2.
- Thread 2 enters
getInstance()
the method.
- Since
instance
it is stillnull
, thread 2 tries to acquire the lock at //1. However, thread 2 blocks at //1 because thread 1 holds the lock.
- Thread 2 is preempted by Thread 1.
- Thread 1 executes, and since the instance is still at //2
null
, thread 1 also creates anSingleton
object and assigns its reference toinstance
.
- Thread 1 exits
synchronized
the block andgetInstance()
returns the instance from the method.
- Thread 1 is preempted by Thread 2.
- Thread 2 acquires the lock at //1 and checks
instance
if it isnull
.
- Since
instance
nonull
, the secondSingleton
object is not created, and the object created by thread 1 is returned.
The theory behind double checked locking is perfect. Unfortunately, the reality is quite different. The problem with double-checked locking is that there is no guarantee that it will run smoothly on a uniprocessor or multiprocessor computer.
The failure of double-checked locking is not due to an implementation bug in the JVM, but to the Java platform memory model. The memory model allows for so-called "out-of-order writes", and this is a major reason why these idioms fail.
write out of order
To explain this, the line //3 in Listing 4 above needs to be revisited. This line of code creates an Singleton
object and initializes variables instance
to reference this object. The problem with this line of code is that the variable may become negated Singleton
before the constructor body is executed .instance
null
What? This statement may surprise you, but it is true. Before explaining how this phenomenon occurs, please temporarily accept this fact, let's first examine how double-checked locking is broken. Suppose the code in Listing 4 executes the following sequence of events:
- Thread 1 enters
getInstance()
the method.
- Thread 1 enters the block at //1 because
instance
of .null
synchronized
- Thread 1 advances to //3, but before the constructor executes , negates the instance
null
.
- Thread 1 is preempted by Thread 2.
- Thread 2 checks if the instance is
null
. Because the instance is not null, thread 2instance
returns a reference to a fully constructed but partially initializedSingleton
object.
- Thread 2 is preempted by Thread 1.
- Thread 1 completes the initialization of the object by running
Singleton
the object's constructor and returning a reference to it.
This sequence of events occurs when thread 2 returns an object whose constructor has not yet been executed.
To show this happening, assume instance =new Singleton();
the following pseudocode is executed for the code line: instance =new Singleton();
mem = allocate(); //Allocate memory for Singleton object. instance = mem; //Note that instance is now non-null, but //has not been initialized. ctorSingleton(instance); //Invoke constructor for Singleton passing //instance.
This pseudocode is not only possible, but actually happens with some JIT compilers. The order of execution is reversed, but given the current memory model, this is allowed to happen. This behavior of the JIT compiler makes the problem of double-checked locking nothing more than an academic exercise.
To illustrate this, assume the code in Listing 5. It contains a stripped version of getInstance()
the method. I've removed the "double-checking" to simplify our review of the generated assembly code (Listing 6). We only care about how the JIT compiler compiles instance=new Singleton();
the code. Additionally, I provide a simple constructor to explicitly illustrate how that constructor works in assembly code.
Listing 5. Singleton class to demonstrate out-of-order writes
class Singleton { private static Singleton instance; private boolean inUse; private int val; private Singleton() { inUse = true; val = 5; } public static Singleton getInstance() { if (instance == null) instance = new Singleton(); return instance; } }
getInstance()
Listing 6 contains the assembly code generated by the Sun JDK 1.2.1 JIT compiler for the method body in Listing 5 .
Listing 6. Assembly code generated from the code in Listing 5
;asm code generated for getInstance 054D20B0 mov eax,[049388C8] ;load instance ref 054D20B5 test eax,eax ;test for null 054D20B7 jne 054D20D7 054D20B9 mov eax,14C0988h 054D20BE call 503EF8F0 ;allocate memory 054D20C3 mov [049388C8],eax ;store pointer in ;instance ref. instance ;non-null and ctor ;has not run 054D20C8 mov ecx,dword ptr [eax] 054D20CA mov dword ptr [ecx],1 ;inline ctor - inUse=true; 054D20D0 mov dword ptr [ecx+4],5 ;inline ctor - val=5; 054D20D7 mov ebx,dword ptr ds:[49388C8h] 054D20DD jmp 054D20B0
NOTE: To refer to the lines of assembly code in the following instructions, I will refer to the last two values of the instruction address since they both begin with 054D20
. For example, B5
rep test eax,eax
.
Assembly code is generated by running a testgetInstance()
program that calls methods in an infinite loop . While the program is running, run the Microsoft Visual C++ debugger and attach it to the Java process representing the test program. Then, break out of execution and find the assembly code that represents that infinite loop.
B0
B5
The first two lines of assembly code at and load the instance
reference from the memory location into and check it. This corresponds to the first line of code in the method in Listing 5 . The first time this method is called, the code executes up to . The code at allocates memory for the object from the heap and stores a pointer to that block of memory in it . The next line of code, , takes the pointer in and stores it back in memory at the instance reference. The result is that it is now NOT and references a valid object. However, the constructor for this object has not yet run, which is exactly what breaks double-checked locking. Then, at the line, the pointer is dereferenced and stored into . The and lines represent inlined constructors that store values and stores to objects. If this code is interrupted by another thread after the execution line and before the constructor completes, the double-checked locking will fail.049388C8
eax
null
getInstance()
instance
null
B9
BE
Singleton
eax
C3
eax
049388C8
instance
null
Singleton
C8
instance
ecx
CA
D0
true
5
Singleton
C3
Not all JIT compilers generate the above code. Some generate code such that negates only after the constructor instance
executes null
. Version 1.3 of the IBM SDK for Java technology and Sun JDK 1.3 both generate such code. However, this does not mean that double-checked locking should be used in these instances. There are some other reasons why this idiom fails. Also, you don't always know which JVMs your code will run on, and JIT compilers can always change to generate code that breaks this idiom.
Double-checked locking: acquires two
Given that the current double-checked locking doesn't work, I included another version of the code, shown in Listing 7, to prevent the out-of-order write problem you just saw.
Listing 7. Attempt to solve the out-of-order write problem
public static Singleton getInstance() { if (instance == null) { synchronized(Singleton.class) { //1 Singleton inst = instance; //2 if (inst == null) { synchronized(Singleton.class) { //3 inst = new Singleton(); //4 } instance = inst; //5 } } } return instance; }
Looking at the code in Listing 7, you should realize that things are getting a little ridiculous. Remember that double checked locking was created to avoid synchronization on simple three line getInstance()
methods. The code in Listing 7 becomes unruly. Also, that code doesn't solve the problem. Careful inspection will reveal the reason.
This code tries to avoid the out-of-order write problem. It tries to solve this by introducing local variables inst
and a second block. synchronized
The theory is implemented as follows:
- Thread 1 enters
getInstance()
the method.
- Thread 1 enters the first block at //1 because
instance
of .null
synchronized
- The value that the local variable
inst
getsinstance
, which is at //2null
.
- Because
inst
ofnull
, thread 1 enters the secondsynchronized
block at //3.
- Thread 1 then starts executing the code at //4 while negating
inst
,null
butSingleton
before the constructor of //4 is executed. (This is the out-of-order write problem we just saw.)
- Thread 1 is preempted by Thread 2.
- Thread 2 enters
getInstance()
the method.
- Because
instance
ofnull
, thread 2 tries to enter the firstsynchronized
block at //1. Since thread 1 currently holds the lock, thread 2 is blocked.
- Thread 1 then completes execution at //4.
- Thread 1 then assigns a fully constructed
Singleton
object to the variable at //5instance
and exits bothsynchronized
blocks.
- Thread 1 returns
instance
.
instance
Then execute thread 2 and assign to at //2inst
.
- Thread 2 finds out
instance
that it is notnull
, and returns it.
The key line here is //5. This line should ensure instance
that only null
one complete Singleton
object is constructed for or referenced. The problem occurs when theory and practice run counter to each other.
The code in Listing 7 is invalid due to the definition of the current memory model. The Java Language Specification ( JLS) mandates synchronized
that code within a block cannot be moved out. synchronized
However, it doesn't say that code outside the block cannot be moved synchronized
into the block.
The JIT compiler will see an optimization opportunity here. This optimization removes the code at //4 and //5, combining and generating the code shown in Listing 8.
Listing 8. The optimized code from Listing 7.
public static Singleton getInstance() { if (instance == null) { synchronized(Singleton.class) { //1 Singleton inst = instance; //2 if (inst == null) { synchronized(Singleton.class) { //3 //inst = new Singleton(); //4 instance = new Singleton(); } //instance = inst; //5 } } } return instance; }
If you do this optimization, you will have the same out-of-order write problem we discussed earlier.
How about declaring every variable volatile?
Another idea is to target variables inst
as well as instance
use keywords volatile
. According to the JLS (see Related topics), declared volatile
variables are considered sequentially consistent, ie, not reordered. But trying to volatile
fix the problem of double-checked locking with the following two problems:
- The problem here is not about sequential consistency, but that the code was moved, not reordered.
- Even when sequential consistency is considered, most JVMs do not implement it correctly
volatile
.
The second point deserves further discussion. Assume the code in Listing 9:
Listing 9. Sequential consistency using volatile
class test { private volatile boolean stop = false; private volatile int num = 0; public void foo() { num = 100; //This can happen second stop = true; //This can happen first //... } public void bar() { if (stop) num += num; //num can == 0! } //... }
According to the JLS, since stop
and num
are declared as volatile
, they should be sequentially consistent. This means that if stop
it ever was true
, num
it must have been set to 100
. However, because of sequential consistency features that many JVMs do not implement volatile
, you cannot rely on this behavior. Therefore, if thread 1 calls foo
and thread 2 calls concurrently bar
, thread 1 may set to before num
it is set to . This will cause the thread to see yes while still being set to . There are additional issues with using atomic numbers with 64-bit variables, but these are beyond the scope of this article. See Resources for more information on this topic.100
stop
true
stop
true
num
0
volatile
solution
The bottom line is this: double-checked locking should not be used in any form, because you cannot guarantee that it will work smoothly on any JVM implementation. JSR-133 is about memory model addressing issues, however, the new memory model will not support double-checked locking. Therefore, you have two options:
- Accept the method shown in Listing 2
getInstance()
for synchronization.
- Abandon synchronization and use a
static
field instead.
Option 2 is shown in Listing 10
Listing 10. Singleton implementation using static fields
class Singleton { private Vector v; private boolean inUse; private static Singleton instance = new Singleton(); private Singleton() { v = new Vector(); inUse = true; //... } public static Singleton getInstance() { return instance; } }
The code in Listing 10 does not use synchronization and ensures that static getInstance()
the method is not created until it is called Singleton
. This is a great option if your goal is to eliminate syncing.
String is not immutable
Given the problem of out-of-order writes and references becoming negated before the constructor executes null
, you might consider String
classes. Suppose you have the following code:
private String str; //... str = new String("hello");
String
Classes should be immutable. Still, given the out-of-order write problem we discussed earlier, would that cause a problem here? The answer is yes. Consider two thread accesses String str
. A thread can see str
a reference to an String
object in which the constructor has not yet run. In fact, Listing 11 contains code that shows this happening. Note that this code only fails on the older JVM I tested it with. Both IBM 1.3 and Sun 1.3 JVMs generate unchanged as expected String
.
Listing 11. Example of mutable String
class StringCreator extends Thread { MutableString ms; public StringCreator(MutableString muts) { ms = muts; } public void run() { while(true) ms.str = new String("hello"); //1 } } class StringReader extends Thread { MutableString ms; public StringReader(MutableString muts) { ms = muts; } public void run() { while(true) { if (!(ms.str.equals("hello"))) //2 { System.out.println("String is not immutable!"); break; } } } } class MutableString { public String str; //3 public static void main(String args[]) { MutableString ms = new MutableString(); //4 new StringCreator(ms).start(); //5 new StringReader(ms).start(); //6 } }
This code creates a MutableString
class at //4 that contains a String
reference shared by the two threads at //3. StringCreator
At lines //5 and //6, two objects and are created on two separate threads StringReader
. Pass in a reference to an MutableString
object. StringCreator
The class enters an infinite loop and creates String
the object at //1 with the value "hello". StringReader
Also enters an infinite loop, and checks at //2 String
whether the value of the current object is "hello". If not, StringReader
the thread prints a message and stops. If String
the class is immutable, you should see no output from this program. If an out-of-order write problem occurs, the only way to StringReader
see str
a reference is never String
an object with a value of "hello".
Running this code on an older JVM such as Sun JDK 1.2.1 will cause out-of-order write issues. and thus result in a non-invariant String
.
conclusion
To avoid costly synchronization in singletons, programmers were very clever and invented the double-checked locking idiom. Unfortunately, given the current memory model, this idiom has not yet become widely used, and it is clearly an unsafe programming construct. Work in this area of redefining the fragile memory model is ongoing. Still, even in the newly proposed memory model, double-checked locking is ineffective. The best solution to this problem is to accept synchronization or use one static field
.
References
-
- You can read the original English version of this article on the developerWorks global site .
- In Peter Haggar's book Practical Java Programming Language Guide (Addison-Wesley, 2000), he covers several Java programming topics, including an entire chapter on multithreading issues and programming techniques.
- The Java Language Specification, Second Edition by Bill Joy et al. (Addison-Wesley, 2000) is the definitive technical reference on the Java programming language.
- The Java Virtual Machine Specification, Second Edition (Addison-Wesley, 1999) , by Tim Lindholm and Frank Yellin, is the definitive document on the Java compiler and runtime environment.
- Visit Bill Pugh's Java Memory Model Web site for a wealth of information on this topic.
- To learn more about
volatile
and 64-bit variants, see Peter Haggar's article "Does Java Guarantee Thread Safety?" in the June 2002 issue of Dr. Dobb's Journal .
- JSR-133 deals with revisions to the Java platform's memory model and threading specification.
- Java software consultant Brian Goetz explains when to use synchronization in " Threading made easy: Synchronization is not the enemy " ( developerWorks , July 2001).
- In " Threading made easy: Sometimes unsharing is best " ( developerWorks , October 2001), Brian Goetz introduces it
ThreadLocal
, and offers some tips for exploiting its power.
- In " Threading made easy: Synchronization is not the enemy " ( developerWorks , February 2001), Alex Roetter introduces the Java Thread API, provides an overview of issues associated with multithreading, and provides solutions to common problems.
- Allen Holub proposes major changes and additions to the Java language in " If I Were King: Proposals for solving threading problems in the Java programming language " ( developerWorks , October 2000).
- Find additional Java technology material in the developerWorks Java technology zone .
- You can read the original English version of this article on the developerWorks global site .