Double Lock Learning--double-checked locking:clever, but broken does you know what synchronized really means?

Source: Internet
Author: User
Tags semaphore java se

From the highly regarded Elements of Java Style to the pages of Javaworld (see Java Tip), many well-m Eaning Java Gurus encourage the use of the double-checked locking (DCL) idiom. There ' s only one problem with it – this clever-seeming idiom.

double-checked locking can be hazardous to your code! This week JavaworldFocuses on the dangers of the double-checked locking idiom. Read seemingly harmless shortcut can wreak havoc on your code:what are DCL?

The DCL idiom is designed to support for the lazy initialization, which occurs when a class defers initialization of an owned obj ECT until it is actually needed:

class SomeClass {  privatenull;    Public Resource getresource () {    ifnull)      new  Resource ( );     return resource;}  }

Security issues has a long bedeviled users of Oracle's Java SE, and on Monday the FTC's efforts to

Why would 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 the case, you can avoid creating the  Resource  entirely. Regardless, the  someclass  object can be created faster if it doesn ' t has 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 SomeClass 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() synchronized .

Unfortunately, synchronized methods run much slower--as much as the times slower--than ordinary unsynchronized methods . One of the motivations for lazy initialization are efficiency, but it appears so in order to achieve faster program start Slower execution time once the program starts. That's 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 = ;  public   Resource GetResource () { Span style= "color: #0000ff;" >if  (Resource = = null  ) { synchronized   { if  (resource = null  ) resource  = new   Resource ();  }}  return   Resource; }}

After the first call getResource() to, is resource already initialized, which avoids the synchronization hits in the most common code Path. DCL also averts the race condition by checking resource a second time inside the synchronized block; NE thread would try to initialize resource . DCL seems like a clever optimization – but it doesn ' t work.

Meet the Java Memory modelrecent Java how-tos

More accurately, DCL isn't guaranteed to work. To understand why, we need-look at the relationship between the JVM and the computer environment on which it runs. In particular, we need-look at the Java Memory Model (JMM), defined in Chapter of the Java Language specificatio n, by Bill Joy, Guy Steele, James Gosling, and Gilad Bracha (Addison-wesley, $), which details how Java handles th e Interaction between threads and memory.

Unlike most other languages, Java defines it relationship to the underlying hardware through a formal memory model that I s expected to hold on all Java platforms, enabling Java ' s promise of "Write Once, Run Anywhere." By comparison, the 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 are quite simple, or at Least it appears so. Programs store items into memory locations and expect that they would still be there the next time those memory locations a Re examined.

Actually, the truth is quite different, but a complicated illusion maintained by the compiler, the JVM, and the hardware H Ides it from us. Though we think of programs as executing sequentially-in the order specified by the program code – that's doesn ' t always happen. Compilers, processors, and caches is free to take all sorts of the liberties with our programs and data, as long as they don ' T affect the result of the computation. For example, compilers can generate instructions on 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 commits to main memory. The JMM says, all of these various reorderings and optimizations is acceptable, so long as the environment maintainsas-if-serialSemantics-that's, so long as you achieve the same result as you would has if the instructions were executed in a stri ctly sequential environment.

POPULAR on Javaworld

Compilers, processors, and caches rearrange the sequence of program operations in order to achieve higher. In recent years, we ' ve seen tremendous improvements in computing performance. While increased processor clock rates has contributed substantially to higher performance, increased parallelism (in the form of pipelined and superscalar execution units, dynamic instruction scheduling and speculative execution, and sophistic ated Multilevel memory caches) has also been a major contributor. At the same time, the task of writing compilers have grown much more complicated, as the compiler must shield the programme R from these complexities.

When writing single-threaded programs, you cannot see the effects of these various instruction or memory operation reorder Ings. However, with multithreaded programs, the situation was quite different-one thread can read memory locations that anothe R thread has written. If thread A Modifies some variables in a certain order, in the absence of synchronization, thread B could not see them in th E same order--or may not be 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 locat Ions in a different order than the one in which they were written. Whatever the circumstances, multithreaded programs is inherently less predictable, unless yoU explicitly ensure that threads has a consistent view of memory by using synchronization.

What does synchronized really mean?

Java treats each thread as if it runs in its own processor with its own local memory, each talking to and synchronizing WI Th a shared main memory. Even on a single-processor system, which model makes sense because of the effects of memory caches and the use of processor Registers to store variables. When a thread modifies a is at its local memory, which modification should eventually show up in the main memory as W ell, and the JMM defines the rules for when the JVM must transfer data between local and main memory. The Java architects realized that a overly restrictive memory model would seriously undermine program performance. They attempted to craft a memory model the would allow programs to perform well on modern computer hardware while still p Roviding guarantees that would allow threads to interact in predictable ways.

Java ' s primary tool for rendering interactions between threads predictably are 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 + 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 Statu S 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 Synchronizati On between the thread ' s local memory and main memory. (Some processors-like the Alpha-has 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, block to main memory before releasing the lock. Similarly, when entering a  synchronized  block, it performs a read barrier--it's as if the local Memory have been invalidated, and it must fetch any variables that would be referenced in the block from main memory.

Understanding: The semantics of synchronized can be executed in diametrically opposite ways depending on the state of the signal.

The proper use of synchronization guarantees that one thread would see the effects of the another in a predictable manner. Only if threads A and B synchronize on the same object would the JMM guarantee that thread B sees the changes made by THR EAD A, and that changes made by thread A inside the synchronized block appearatomically to Thread B (either the whole BL Ock executes or none of it does.) Furthermore, the JMM ensures that synchronized blocks that synchronize on the same object would appear to execute in the same Orde R as they do in the program.

So what's broken about DCL?

DCL relies on a unsynchronized use of the  resource  field. That's appears to being harmless, but it's not. To see why, imagine this 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 would be allocated; The constructor for  Resource  will is called, initializing the member fields of the new object; and the field  resource  of  someclass  will be assigned a reference to the Newly created object.

However, since thread B is not executing inside a  synchronized  block, it could see these memory op Erations in a different order than the one thread a executes. It could be the case that B sees these events in the following order (and the compiler are also free to reorder the Instruc tions: Allocate memory, assign reference to  resource , call constructor. Suppose thread B comes along after the memory have been allocated and the  resource  field is set, but Before the constructor is called. It sees that  resource  is not NULL, skips the  synchronized  block, and Returns a reference to a partially constructed  Resource ! Needless to say, the result is neither expected nor desired.

When presented with this example, many people is skeptical at first. Many highly intelligent programmers has tried to fix DCL so that it does work, but none of these supposedly fixed version s work either. It should is noted that DCL might, in fact, work on some versions of some JVMs--as few JVMs actually implement the JMM p Roperly. However, you don ' t want the correctness of your programs to rely on implementation details--especially errors--specifi C to the particular version of the particular JVM, you use.

Other concurrency hazards is embedded in the DCL--and in any unsynchronized reference to memory written by another thread, Even harmless-looking reads. Suppose thread A has completed initializing the and Resource exits the synchronized block as thread B enters getResource() . Now Resource the are fully initialized, and thread A flushes its local memory off to main memory. resourcethe ' s fields could reference other objects stored in memory through their member fields, which would also be flushed out. While thread B could see a valid reference to the newly created Resource , because it didn ' t perform a read barrier, it could st Ill see stale values of resource ' s member fields.

Volatile doesn ' t mean what are you think, either

A commonly suggested Nonfix is to declare the resource field of SomeClass as volatile . However, while the JMM prevents writes to volatile variables from being reordered with respect to one another and ensures That they is flushed to main memory immediately, it still permits reads and writes of volatile variables to be reordered With respect to nonvolatile reads and writes. That means--unless all fields is as well Resource volatile --thread B can still perceive the constructor ' s effect as Happenin G after are resource set to reference the newly created Resource .

Alternatives to DCL

The most effective-to-fix the DCL idiom is to avoid it. The simplest-avoid it, the course, is-to-use synchronization. Whenever a variable written by one thread was being read by another, you should use synchronization to guarantee that MODIF Ications is visible to and threads in a predictable manner.

Another option for avoiding the problems with DCL are to drop lazy initialization and instead use eager initialization< /c0>. Rather than delay initialization resource of until it is first used, initialize it at construction. The class loader, which synchronizes on the classes ' Class object, executes static initializer blocks at class Initializat Ion time. That means, the effect of static initializers is automatically visible to all threads as soon as the class loads.

One special case of a lazy initialization that does work as expected without synchronization is the static singleton. When the initialized object was a static field of a class with no other methods or fields, the JVM effectively performs Laz Y initialization automatically. In the following example, the'll isn't being Resource constructed until the field resource is first referenced by another class, and Any memory writes this result from resource ' s initialization is automatically visible to all threads:

class Mysingleton {  publicstaticnew  Resource ();}

The initialization is performed when the JVM initializes the class. Since have no other fields MySingleton or methods, class initialization occurs when the resource field is first referenced.

DCL also works with 32-bit primitive values. If resource SomeClass The field in were an integer (but not a long or a double), then SomeClass would behave as expected. However, you cannot use this behavior to fix the problems with DCL when you want to lazily initialize an object reference or more than one primitive value.

Would the JMM be fixed?

Bill Pugh of the University of Maryland is preparing a Java specification Request (JSR) to fix some of the problems with T He current JMM. These improvements include:

    • Clarification of the JMM
    • Relaxation of some requirements to allow for common compiler optimizations
    • Reduction of synchronization ' s performance impact in the absence of real contention
    • Modifications to's volatile semantics in order to prevent reordering of writes to volatile variables with writes to other Var Iables

At the time of this writing, this JSR have not yet entered the Java Community Process.

Conclusion

The JMM is the first attempt to define shared memory semantics in the context of the language specification for a general -purpose programming language. Unfortunately, it's quite complicated, poorly understood, and not consistently implemented across JVMs. Not surprisingly, the JMM ' s complexity resulted in JVM implementation errors and widespread misunderstandings among users --such synchronized as the misperception of how works, which leads to unsafe idioms like DCL. While there is proposals on the table for a simpler memory model, it'll be is quite a while before we see such fundamental Changes to the Java specification, if ever.

In the meantime, carefully examine your multithreaded programs to ensure so any reference to a variable written by anoth Er thread is properly synchronized.

Brian Goetz is a professional software developer with over years of experience. He is a principal consultant at Quiotix, a software development and consulting firm located in Los Altos, Calif.

Double Lock Learning--double-checked locking:clever, but broken does you know what synchronized really means?

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.