The DCL idiom was designed to support lazy initialization, which occurs when a class defers initialization of an owned object until it is actually needed:
class SomeClass {
private Resource resource = null;
public Resource getResource() {
if (resource == null)
resource = new Resource();
return resource;
}
}
Why would you want to defer initialization? Perhaps creating a Resource is an expensive operation, and users of SomeClass might not actually call getResource() in any given run. In that case, you can avoid creating the Resource entirely. Regardless, the SomeClass object can be created faster if it doesn't have to also create a Resource at construction time. Delaying some initialization operations until a user actually needs their results can help programs
start up faster.
What if you try to use SomeClass in a multithreaded application? Then a race condition results: two threads could simultaneously execute the test to see if
resource is null and, as a result, initialize resource twice. In a multithreaded environment, you should declare getResource() to be synchronized.
Unfortunately, synchronized methods run much slower -- as much as 100 times slower -- than ordinary unsynchronized methods. One of the motivations for lazy initialization is efficiency, but it appears that in order to achieve faster program startup, you have to accept slower execution time once the program starts. That doesn't sound like a great trade-off.
DCL purports to give us the best of both worlds. Using DCL, the getResource() method would look like this:
class SomeClass {
private Resource resource = null;
public Resource getResource() {
if (resource == null) {
synchronized {
if (resource == null)
resource = new Resource();
}
}
return resource;
}
}
After the first call to getResource(), resource is already initialized, which avoids the synchronization hit in the most common code path. DCL also averts the race condition
by checking resource a second time inside the synchronized block; that ensures that only one thread will try to initialize resource. DCL seems like a clever optimization -- but it doesn't work.
More accurately, DCL is not guaranteed to work. To understand why, we need to look at the relationship between the JVM and the computer environment on which it runs. In particular, we need to look at the Java Memory Model (JMM), defined in Chapter 17 of the Java Language Specification, by Bill Joy, Guy Steele, James Gosling, and Gilad Bracha (Addison-Wesley, 2000), which details how Java handles the interaction between threads and memory.
synchronized
Read aboveBy Anonymous on January 25, 2010, 7:32 pmThe links in the above comments show that this won't work in java < 1.5. The violtile memory trick is a java 1.5 thing. Also, adding a layer of method calls...
Reply | Read entire comment
What's broken: DCL or your example?By o.cornu on January 21, 2010, 1:10 pmIf i get your point right, your problem comes from *instantiating* Resource, which makes resource = new Resource(); _not_ an atomic operation (no matter resource...
Reply | Read entire comment
Synchronization no longer so slowBy Anonymous on December 1, 2009, 8:34 amYou need to update the assertion that synchronized methods are up to 100x slower than unsynchronized methods. According to which benchmarks, against which JVM? Synchronization...
Reply | Read entire comment
"While thread B may see a valid reference to the newly created RBy Anonymous on September 30, 2009, 3:12 am"While thread B may see a valid reference to the newly created Resource, because it didn't perform a read barrier, it could still see stale values of resource's...
Reply | Read entire comment
No, your implementation isBy Anonymous on September 11, 2009, 9:52 pmNo, your implementation is not safe. Remember, there is no guarantee that 'foo = new Foo();' will excute before 'h =1;'. If it doesn't, then the second thread might...
Reply | Read entire comment
View all comments