Programming Tutorials Browser Tutorials Articles Struts Tutorials Hibernate Tutorials

  Tutorial: Double-checked locking: Clever, but broken - JavaWorld February 2001

Double-checked locking: Clever, but broken - JavaWorld February 2001

Tutorial Details:

Double-checked locking: Clever, but broken
Double-checked locking: Clever, but broken
By: By Brian Goetz
Do you know what synchronized really means?
rom the highly regarded Elements of Java Style to the pages of JavaWorld (see Java Tip 67 ), many well-meaning Java gurus encourage the use of the double-checked locking (DCL) idiom. There's only one problem with it -- this clever-seeming idiom may not work.
Double-checked locking can be hazardous to your code!
This week JavaWorld focuses on the dangers of the double-checked locking idiom. Read more about how this seemingly harmless shortcut can wreak havoc on your code:
"Warning! Threading in a multiprocessor world," Allen Holub
Double-checked locking: Clever, but broken," Brian Goetz
To talk more about double-checked locking, go to Allen Holub's Programming Theory & Practice discussion
What is DCL?
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.
Meet the Java Memory Model
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.
Unlike most other languages, Java defines its relationship to the underlying hardware through a formal memory model that is expected to hold on all Java platforms, enabling Java's promise of "Write Once, Run Anywhere." By comparison, other languages like C and C++ lack a formal memory model; in such languages, programs inherit the memory model of the hardware platform on which the program runs.
When running in a synchronous (single-threaded) environment, a program's interaction with memory is quite simple, or at least it appears so. Programs store items into memory locations and expect that they will still be there the next time those memory locations are examined.
Actually, the truth is quite different, but a complicated illusion maintained by the compiler, the JVM, and the hardware hides it from us. Though we think of programs as executing sequentially -- in the order specified by the program code -- that doesn't always happen. Compilers, processors, and caches are free to take all sorts of liberties with our programs and data, as long as they don't affect the result of the computation. For example, compilers can generate instructions in a different order from the obvious interpretation the program suggests and store variables in registers instead of memory; processors may execute instructions in parallel or out of order; and caches may vary the order in which writes commit to main memory. The JMM says that all of these various reorderings and optimizations are acceptable, so long as the environment maintains as-if-serial semantics -- that is, so long as you achieve the same result as you would have if the instructions were executed in a strictly sequential environment.
Compilers, processors, and caches rearrange the sequence of program operations in order to achieve higher performance. In recent years, we've seen tremendous improvements in computing performance. While increased processor clock rates have contributed substantially to higher performance, increased parallelism (in the form of pipelined and superscalar execution units, dynamic instruction scheduling and speculative execution, and sophisticated multilevel memory caches) has also been a major contributor. At the same time, the task of writing compilers has grown much more complicated, as the compiler must shield the programmer from these complexities.
When writing single-threaded programs, you cannot see the effects of these various instruction or memory operation reorderings. However, with multithreaded programs, the situation is quite different -- one thread can read memory locations that another thread has written. If thread A modifies some variables in a certain order, in the absence of synchronization, thread B may not see them in the same order -- or may not see them at all, for that matter. That could result because the compiler reordered the instructions or temporarily stored a variable in a register and wrote it out to memory later; or because the processor executed the instructions in parallel or in a different order than the compiler specified; or because the instructions were in different regions of memory, and the cache updated the corresponding main memory locations in a different order than the one in which they were written. Whatever the circumstances, multithreaded programs are inherently less predictable, unless you explicitly ensure that threads have a consistent view of memory by using synchronization.
What does synchronized really mean?
Java treats each thread as if it runs on its own processor with its own local memory, each talking to and synchronizing with a shared main memory. Even on a single-processor system, that model makes sense because of the effects of memory caches and the use of processor registers to store variables. When a thread modifies a location in its local memory, that modification should eventually show up in the main memory as well, and the JMM defines the rules for when the JVM must transfer data between local and main memory. The Java architects realized that an overly restrictive memory model would seriously undermine program performance. They attempted to craft a memory model that would allow programs to perform well on modern computer hardware while still providing guarantees that would allow threads to interact in predictable ways.
Java's primary tool for rendering interactions between threads predictably is the synchronized keyword. Many programmers think of synchronized strictly in terms of enforcing a mutual exclusion semaphore ( mutex ) to prevent execution of critical sections by more than one thread at a time. Unfortunately, that intuition does not fully describe what synchronized means.
The semantics of synchronized do indeed include mutual exclusion of execution based on the status of a semaphore, but they also include rules about the synchronizing thread's interaction with main memory. In particular, the acquisition or release of a lock triggers a memory barrier -- a forced synchronization between the thread's local memory and main memory. (Some processors -- like the Alpha -- have explicit machine instructions for performing memory barriers.) When a thread exits a synchronized block, it performs a write barrier -- it must flush out any variables modified in that block to main memory before releasing the lock. Similarly, when entering a synchronized block, it performs a read barrier -- it is as if the local memory has been invalidated, and it must fetch any variables that will be referenced in the block from main memory.
The proper use of synchronization guarantees that one thread will see the effects of another in a predictable manner. Only when threads A and B synchronize on the same object will the JMM guarantee that thread B sees the changes made by thread A, and that changes made by thread A inside the synchronized block appear atomically to thread B (either the whole block executes or none of it does.) Furthermore, the JMM ensures that synchronized blocks that synchronize on the same object will appear to execute in the same order as they do in the program.
So what's broken about DCL?
DCL relies on an unsynchronized use of the resource field. That appears to be harmless, but it is not. To see why, imagine that thread A is inside the synchronized block, executing the statement resource = new Resource(); while thread B is just entering getResource() . Consider the effect on memory of this initialization. Memory for the new Resource object will be al


 

Read Tutorial at: Click here to view the tutorial

Rate Tutorial:
Double-checked locking: Clever, but broken - JavaWorld February 2001

View Tutorial:
Double-checked locking: Clever, but broken - JavaWorld February 2001

Related Tutorials:

Java Q&A - Java Still Open
Java Q&A - Java Still Open
 
XML messaging, Part 3
XML messaging, Part 3
 
Can ThreadLocal solve the double-checked locking problem?
Can ThreadLocal solve the double-checked locking problem?
 
Jini-like discovery for RMI
Jini-like discovery for RMI
 
Connect the enterprise with the JCA, Part 1
Connect the enterprise with the JCA, Part 1
 
Cut down on logging errors with Jylog
Cut down on logging errors with Jylog
 
Exceptions: Don't get thrown for a loss
Exceptions: Don't get thrown for a loss
 
My kingdom for a good timer!
My kingdom for a good timer!
 
Very interesting
Very interesting
 
Java and GIS, Part 2: Mobile LBS
Java and GIS, Mobile LBS Using LBS First, let\'s make sure that we understand what an LBS application is. Typically, an LBS application is trying to answer the question \"Where am I?\" and then do something with that information. There are a number
 
Filtering and Transforming Digital Images
Filtering and Transforming Digital Images In this Issue Welcome to the Core Java Technologies Tech Tips for April 7, 2004. Here you\'ll get tips on using core Java technologies and APIs, such as those in Java 2 Platform, Standard Edition (J2SE).
 
WS-Specifications
WS-Specifications The WS-Specifications build a composable architecture to form an environment for complex Web Service applications. Different vendors, such as BEA, IBM, Microsoft, RSA Security and SAP, have joined forces to lay the foundation of secure
 
IberAgents
Introduction IberAgents is a web application framework that enables the creation of SOAP-interoperable components in Java, with life cycle management and remote configuration. Development started in 2001; we now have a mature, solid open-source platform
 
Definition of Bioinformatics
Definition of Bioinformatics Definition of Bioinformatics About Bioinformatics In February 2001, the human genome was finally deciphered! In other words, scientists have succeeded in reading the chain of more than 3 billion base pairs that
 
Web Hosting Guide. What is Web Hosting Plan?
Web Hosting Guide. What is Web Hosting Plan? What is Web Hosting Plan? Web hosting plan is the different plans provided by Hosting Companies for hosting your web site. Web hosting plans include the storage limit, bandwidth, access to server
 
Roadmap to Sun Developer Documentation
This article offers an array of information sources about Sun products for developers, particularly those on docs.sun.com. Topics include the Solaris Operating System, Sun Studio tools, and other products and technologies.
 
New Technical Articles: 64-bit Programming on Solaris 10 OS for x86 Platforms
Four technical articles describe the new Sun Studio 10 software's 64-bit programming features on the Solaris 10 OS for x86 and AMD64 platforms. Important issues regarding the AMD64 ABI (Application Binary Interface), debugging, migration to 64-bits, and p
 
News: World Records Broken with Sun Studio 10 Software and the Solaris 10 OS
Sun submits new performance world record for Sun Studio 10 compilers and the Solaris 10 operating system.
 
Locking Down Server Access to SSH With SunScreen Software (Community Submission)
This Tech Tip shows how to lock down a server to a group of client machines, allowing SSH access only, using SunScreen software.
 
Solaris 10 OS Certification Beta Exams
If you are an expert in system and network administration, you can get involved in the creation of three new Solaris 10 certification exams. These Beta exams count toward official Solaris Certification and allow you to provide comments and technical feedb
 
Site navigation
 

 

Send your comments, Suggestions or Queries regarding this site at roseindia_net@yahoo.com.

Copyright © 2006. All rights reserved.