The "double-checked locking is broker" Declaration

xiaoxiao2021-03-06  55

Signed by: David Bacon (IBM Research) Joshua Bloch (Javasoft), Jeff Bogda, Cliff Click (Hotspot JVM project), Paul Haahr, Doug Lea, Tom May, Jan-Willem Maessen, John D. Mitchell (jGuru) Kelvin Nilsen, Bill Pugh, Emin Gun SIRER

.

Unfortunately, it will not work reliably in a platform independent way when implemented in Java. When implemented in other languages, such as C , it depends on the memory model of the processor, the reorderings performed by the compiler and the interaction between the compiler and the synchronization library. Since none of these are specified in a language such as C , little can be said about the situations in which it will work. Explicit memory barriers can be used to make it work in C , but these barriers are not available in Java.

To First Explain The Desired Behavior, Consider The Following Code:

// Single Threaded Version

Class foo {

Private helper helper = null;

Public helper getHelper () {

IF (helper == null)

Helper = new helper ();

Return helper;

}

// Other functions and members ...

}

If this code was used in a multithreaded context, many things could go wrong. Most obviously, two or more Helper objects could be allocated. (We'll bring up other problems later). The fix to this is simply to synchronize the getHelper ( ) Method:

// Correct Multithreaded Version

Class foo {

Private helper helper = null;

Public synchronized helper getHelper () {

IF (helper == null)

Helper = new helper ();

Return helper;

}

// Other functions and members ...

}

THE CODETED: / / BROKEN MULTITED: // Broken Multithreaded Version

// "Double-Checked Locking" IDIOM

Class foo {

Private helper helper = null;

Public helper getHelper () {

IF (helper == null)

Synchronized (this) {

IF (helper == null)

Helper = new helper ();

}

Return helper;

}

// Other functions and members ...

}

.

IT Doesn't Work

There are lots of reasons it does not work. The first couple of reasons we'll describe are more obvious. After understanding those, you may be tempted to try to devise a way to "fix" the double-checked locking idiom. Your Fixes Will Not Work: There is The aten.

.

The First Reason IT Doesn't Work

The most obvious reason it does not work it that the writes that initialize the Helper object and the write to the helper field can be done or perceived out of order. Thus, a thread which invokes getHelper () could see a non-null reference To a helper object, but see the default values ​​for fields of the value set in the constructor.

If the compiler inlines the call to the constructor, then the writes that initialize the object and the write to the helper field can be freely reordered if the compiler can prove that the constructor can not throw an exception or perform synchronization.Even if the compiler does not For the processor.,, ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,

Doug Lea Has Written A More Detailed Descript of Compiler-based Reorderings.

A Test Case Showing That Doesn't Work

Paul Jakubik Found AN Example of a Use of Double-Checked Locking That Did Not Work Correctly. A Slightly Cleaned Up Version of That Code Is Available Here.

When Run ON A System Using The Symantec Jit, IT Doesn't Work. In Particular, The Symantec Jit Compiles

Singletons [i] .reference = new singleton ();

To The Following (Note That The Symantec Jit Use A Handle-Based Object Allocation System).

0206106A MOV EAX, 0F97E78H

0206106f Call 01f6b210; Allocate Space for

Singleton, Return RESULT IN EAX

02061074 MOV DWORD PTR [EBP], EAX; EBP IS & SINGLETONS [I] .Reference

Store The Unconstructed Object Here.

02061077 MOV ECX, DWORD PTR [EAX]; Dereference The Handle To

Get the Raw Pointer

02061079 MOV DWORD PTR [ECX], 100H; Next 4 LINES ARE

0206107F MOV DWORD PTR [ECX 4], 200h; Singleton's Inlined Construction

02061086 MOV DWORD PTR [ECX 8], 400h

0206108D MOV DWORD PTR [ECX 0CH], 0F84030H

As you can see, the assignment to singletons [i] .reference is performed before the constructor for Singleton is called. This is completely legal under the existing Java memory model, and also legal in C and C (since neither of them have a memory Model) .a fix what Doesn't Work

Given The Explanation Above, a Number of People Have Suggested The Following Code:

// (still) Broken Multithreaded Version

// "Double-Checked Locking" IDIOM

Class foo {

Private helper helper = null;

Public helper getHelper () {

IF (helper == null) {

Helper H;

Synchronized (this) {

H = helper;

IF (h == NULL)

Synchronized (this) {

H = new helper ();

} // Release Inner SYNCHRONIZATION LOCK

Helper = H;

}

}

Return helper;

}

// Other functions and members ...

}

This code puts construction of the Helper object inside an inner synchronized block. The intuitive idea here is that there should be a memory barrier at the point where synchronization is released, and that should prevent the reordering of the initialization of the Helper object and the assignment To The Field Helper.

Unfortunately, that intuition is absolutely wrong. The rules for synchronization do not work that way. The rule for a monitorexit (ie, releasing synchronization) is that actions before the monitorexit must be performed before the monitor is released. However, there is no rule which says that actions after the monitorexit may not be done before the monitor is released It is perfectly reasonable and legal for the compiler to move the assignment helper = h;. inside the synchronized block, in which case we are back where we were previously . Many processors offer instructions that perform this kind of one-way memory barrier. Changing the semantics to require releasing a lock to be a full memory barrier would have performance penalties.More fixes that do not work

There is something you can do to force the writer to perform a full bidirectional memory barrier. This is gross, inefficient, and is almost guaranteed not to work once the Java Memory Model is revised. Do not use this. In the interests of science, I'VE Put A Description of this Technique on a Separate Page. Do Not Use IT.

HoWever, Even with a fulmanory barrier being performed by the thread this initializes the helper object, itsts, it.

The Problem is this.

Why? Because processors have their own locally cached copies of memory. On some processors, unless the processor performs a cache coherence instruction (eg, a memory barrier), reads can be performed out of stale locally cached copies, even if other processors used memory Barriers to Force their Writes INTO GLOBAL MEMORY.

I'VE CREATED A SEPARATE Web Page with a Discussion of How this Can Actually Happen ON AN Alpha Processor.Is IT Worth Trouble?

For most applications, the cost of simply making the getHelper () method synchronized is not high. You should only consider this kind of detailed optimizations if you know that it is causing a substantial overhead for an application.

Very Offerness Rather Than Handling Exchange Sort (See The Specjvm DB Benchmark) Will Have Much More Impact.

Making it work for static singleletons

If the singleton you are creating is static (i.e., there will only be one Helper created), as opposed to a property of another object (e.g., there will be one Helper for each Foo object, there is a simple and elegant solution.

Just define the singleton as a static field in a separate class. The semantics of Java guarantee that the field will not be initialized until the field is referenced, and that any thread which accesses the field will see all of the writes resulting from initializing that field .

Class helperSingleton {

Static helper singleleton = new helper ();

}

IT Will Work for 32-Bit Primitive Values

Although the double-checked locking idiom can not be used for references to objects, it can work for 32-bit primitive values ​​(eg, int's or float's). Note that it does not work for long's or double's, since unsynchronized reads / writes of 64 -bit primitives are not guaranteed to be atomic.

// Correct Double-Checked Locking for 32-Bit Primitives

Class foo {

Private int cachedhashcode = 0;

Public Int hashcode () {

INT H = CachedHashcode;

IF (h == 0)

Synchronized (this) {

IF (CachedHashcode! = 0) Return Cached CachedHashcode; H = computehashcode ();

Cachedhashcode = h;

}

Return H;

}

// Other functions and members ...

}

IN FACT, Assuming That The Computehashcode Function Always Returned The Same Result and Had No Side Effects You Could Even Get Rid of All of the synchronization.

// Lazy Initialization 32-bit Primitives

// Thread-Safe IF ComputeHashcode Is Ideempotent

Class foo {

Private int cachedhashcode = 0;

Public Int hashcode () {

INT H = CachedHashcode;

IF (h == 0) {

H = computehashcode ();

Cachedhashcode = h;

}

Return H;

}

// Other functions and members ...

}

Making it work with explicit memory barriers

It is possible to make the double checked locking pattern work if you have explicit memory barrier instructions For example, if you are programming in C , you can use the code from Doug Schmidt et al's book..:

// C Implementation with explicit memory barriers

// Should Work ON Any Platform, Including Dec Alphas

// from "patterns for concurrent and distributed objects",

// by Doug Schmidt

Template type *

Singleton :: Instance (void) {

// first check

TYPE * TMP = INSTANCE_;

// INSERT THE CPU-Specific Memory Barrier Instruction

// TO SYNCHRONIZE The Cache Lines On Multi-Processor.

ASM ("MemoryBarrier");

IF (TMP == 0) {

// ensure serialization (Guard

// constructor acquires lock_).

Guard Guard (LOCK_);

// Double Check.

TMP = Instance_;

IF (TMP == 0) {

TMP = New Type;

// INSERT THE CPU-Specific Memory Barrier Instruction

// TO SYNCHRONIZE The Cache Lines On Multi-Processor.

ASM ("MemoryBarrier"); instance_ = TMP;

}

Return TMP;

}

FIXING DOUBLE-CHECKED LOCKING THREAD LOCAL Storage

Alexander Terekhov (TEREKHOV@de.ibm.com) came up clever suggestion for implementing double checked locking using thread local storage. Each thread keeps a thread local flag to determine whether that thread has done the required synchronization.

Class foo {

/ ** if perthreadInstance.get () Returns a non-null value, this thread

HAS Done Synchronization Needed to See Initialization

Of helper * /

PRIVATE FINAL THREADLOCAL PERTHREADINSTANCE = New Threadlocal ();

Private helper helper = null;

Public helper getHelper () {

IF ("== null) CreateHelper ();

Return helper;

}

Private final void createhelper () {

Synchronized (this) {

IF (helper == null)

Helper = new helper ();

}

// any non-null value would do as the argument here

PerthReadInstance.Set (PERTHREADInstance);

}

}

The performance of this technique depends quite a bit on which JDK implementation you have. In Sun's 1.2 implementation, ThreadLocal's were very slow. They are significantly faster in 1.3, and are expected to be faster still in 1.4. Doug Lea analyzed the performance of some Techniques for Implementing Lazy Initialization.

Under The Proposed Java Memory Model

Work is underway to revise the java memory model and thread specification.

FIXING DOUBLE-CHECKED LOCKING Using Volatile

The consensus proposal extends the semantics for volatile so that the system will not allow a write of a volatile to be reordered with respect to any previous read or write, and a read of a volatile can not be reordered with respect to any following read or write. See the slides from the JavaOne BOF for more details.With this change, the Double-Checked Locking idiom can be made to work by declaring the helper field to be volatile. This does not work under the current semantics for volatile.

// Works with Acquire / Release Semantics for Volatile

// Broken Under Current Semantics for Volatile

Class foo {

Private Volatile Helper Helper = NULL;

Public helper getHelper () {

IF (helper == null) {

Synchronized (this) {

IF (helper == null)

Helper = new helper ();

}

}

Return helper;

}

}

Double-Checked Locking Immutable Objects

If Helper is an immutable object, such that all of the fields of Helper are final, then double-checked locking will work without having to use volatile fields. The idea is that a reference to an immutable object (such as a String or an Integer .).

This is, in fact, not true under the existing semantics, and even under some existing implementations. The details of the proposed semantics for immutable objects are tricky and still subject to working out lots of fine print.

Descriptions of Double-Check IDiom

转载请注明原文地址:https://www.9cbs.com/read-117429.html

New Post(0)