单例模式“双重检查锁定Double-Checked Locking”线程安全问题

几篇合集。

1 单例模式“双重检查锁定Double-Checked Locking”线程安全问题

https://blog.csdn.net/wabiaozia/article/details/84723899

2 主题:用happen-before规则重新审视DCL

https://blog.csdn.net/wabiaozia/article/details/84727407

3 Double-checked locking: Clever, but broken

http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#dcl

4 Does the new memory model fix the "double-checked locking" problem?

https://blog.csdn.net/wabiaozia/article/details/84839566

注:

1 1和 3.4三篇写的比较早,有些问题已经处理更新了。可以看文章下的一些评论。

2 volatile屏蔽指令重排序的语义在JDK1.5中才被完全修复,此前的JDK中及时将变量声明为volatile,也仍然不能完全避免重排序所导致的问题(主要是volatile变量前后的代码仍然存在重排序问题),这点也是在JDK1.5之前的Java中无法安全使用DCL来实现单例模式的原因
3 在java5之前对final字段的同步语义和其它变量没有什么区别,在java5中,final变量一旦在构造函数中设置完成(前提是在构造函数中没有泄露this引用),其它线程必定会看到在构造函数中设置的值。而DCL的问题正好在于看到对象的成员变量的默认值,因此我们可以将LazySingleton的someField变量设置成final,这样在java5中就能够正确运行了。

以下是原文:

转载请标明连接:https://blog.csdn.net/wabiaozia/article/details/84723899

译文原链接:http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

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, Jeremy Manson, John D. Mitchell (jGuru) Kelvin Nilsen, Bill Pugh, Emin Gun Sirer

Double-Checked Locking is widely cited and used as an efficient method for implementing lazy initialization in a multithreaded environment.

Unfortunately, it will not work reliably in a platform independent way when implemented in Java, without additional synchronization. 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 code above performs synchronization every time getHelper() is called. The double-checked locking idiom tries to avoid synchronization after the helper is allocated:

 

// 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...
  }

Unfortunately, that code just does not work in the presence of either optimizing compilers or shared memory multiprocessors.

It doesn't work

There are lots of reasons it doesn't 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 are more subtle reasons why your fix won't work. Understand those reasons, come up with a better fix, and it still won't work, because there are even more subtle reasons.

Lots of very smart people have spent lots of time looking at this. There is no way to make it work without requiring each thread that accesses the helper object to perform synchronization.

The first reason it doesn't work

The most obvious reason it doesn't 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 helper object, rather than the values 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 cannot throw an exception or perform synchronization.

Even if the compiler does not reorder those writes, on a multiprocessor the processor or the memory system may reorder those writes, as perceived by a thread running on another processor.

Doug Lea has written a more detailed description of compiler-based reorderings.

A test case showing that it 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 using 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 constructor
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 that 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 don't work that way. The rule for a monitorexit (i.e., 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 don't 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 full memory barrier being performed by the thread that initializes the helper object, it still doesn't work.

The problem is that on some systems, the thread which sees a non-null value for the helper field also needs to perform memory barriers.

Why? Because processors have their own locally cached copies of memory. On some processors, unless the processor performs a cache coherence instruction (e.g., 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 the 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 often, more high level cleverness, such as using the builtin mergesort rather than handling exchange sort (see the SPECJVM DB benchmark) will have much more impact.

Making it work for static singletons

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 singleton = new Helper();
  }

It will work for 32-bit primitive values

Although the double-checked locking idiom cannot be used for references to objects, it can work for 32-bit primitive values (e.g., 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 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 (i.e., idempotent), you could even get rid of all of the synchronization.

 

// Lazy initialization 32-bit primitives
// Thread-safe if computeHashCode is idempotent
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 using Thread Local Storage

Alexander Terekhov ([email protected]) 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 (perThreadInstance.get() == 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 new Java Memory Model

As of JDK5, there is a new Java Memory Model and Thread specification.

Fixing Double-Checked Locking using Volatile

JDK5 and later 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 cannot be reordered with respect to any following read or write. See this entry in Jeremy Manson's blog 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 JDK4 and earlier.

 

// 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) should behave in much the same way as an int or float; reading and writing references to immutable objects are atomic.

Descriptions of double-check idiom

  • Reality Check, Douglas C. Schmidt, C++ Report, SIGS, Vol. 8, No. 3, March 1996.
  • Double-Checked Locking: An Optimization Pattern for Efficiently Initializing and Accessing Thread-safe Objects, Douglas Schmidt and Tim Harrison. 3rd annual Pattern Languages of Program Design conference, 1996
  • Lazy instantiation, Philip Bishop and Nigel Warren, JavaWorld Magazine
  • Programming Java threads in the real world, Part 7, Allen Holub, Javaworld Magazine, April 1999.
  • Java 2 Performance and Idiom Guide, Craig Larman and Rhett Guthrie, p100.
  • Java in Practice: Design Styles and Idioms for Effective Java, Nigel Warren and Philip Bishop, p142.
  • Rule 99, The Elements of Java Style, Allan Vermeulen, Scott Ambler, Greg Bumgardner, Eldon Metz, Trvor Misfeldt, Jim Shur, Patrick Thompson, SIGS Reference library
  • Global Variables in Java with the Singleton Pattern, Wiebe de Jong, Gamelan

 

以下是谷歌翻译:

转载请标明连接:https://blog.csdn.net/wabiaozia/article/details/84723899

签名: David Bacon(IBM Research)Joshua Bloch(Javasoft), Jeff Bogda,Cliff Click(Hotspot JVM项目), Paul Haahr, Doug Lea, Tom May, Jan-Willem Maessen,Jeremy Manson, John D. Mitchell(jGuru) Kelvin Nilsen, Bill Pugh, Emin Gun Sirer

Double-Checked Locking被广泛引用并用作在多线程环境中实现延迟初始化的有效方法。

遗憾的是,如果没有额外的同步,它将无法以独立于平台的方式在Java中实现时可靠地工作。当用其他语言(如C ++)实现时,它取决于处理器的内存模型,编译器执行的重新排序以及编译器和同步库之间的交互。由于这些都不是用C ++这样的语言指定的,因此对它的工作情况几乎没有什么可说的。可以使用显式内存屏障使其在C ++中工作,但这些障碍在Java中不可用。

要首先解释所需的行为,请考虑以下代码:

 

//单线程版本
class Foo { 
  private Helper helper = null;
  public Helper getHelper(){
    if(helper == null) 
        helper = new Helper();
    回报助手;
    }
  //其他功能和成员......
  }

如果在多线程上下文中使用此代码,很多事情可能会出错。最明显的是,可以分配两个或更多 Helper对象。(我们稍后会提出其他问题)。解决这个问题只是为了同步getHelper()方法:

 

//更正多线程版本
class Foo { 
  private Helper helper = null;
  public synchronized Helper getHelper(){
    if(helper == null) 
        helper = new Helper();
    回报助手;
    }
  //其他功能和成员......
  }

上面的代码每次 调用getHelper()时都会执行同步。双重检查锁定习惯用法在分配帮助程序后尝试避免同步:

 

//破碎的多线程版本
//“Double-Checked Locking”成语
class Foo { 
  private Helper helper = null;
  public Helper getHelper(){
    if(helper == null) 
      synchronized(this){
        if(helper == null) 
          helper = new Helper();
      }    
    回报助手;
    }
  //其他功能和成员......
  }

不幸的是,该代码在优化编译器或共享内存多处理器的存在下不起作用。

它不起作用

有很多原因它不起作用。我们将描述的前几个原因更为明显。在理解了这些之后,你可能会试图设法一种“修复”双重检查锁定习语的方法。您的修复程序将无法正常工作:您的修复程序无法正常工作的原因更为微妙。了解这些原因,提出更好的解决方案,但它仍然无法正常工作,因为还有更微妙的原因。

很多非常聪明的人花了很多时间看这个。有没有办法让它无需访问辅助对象进行同步每个线程工作。

它不起作用的第一个原因

最明显的原因,它不工作,它认为该初始化写操作助手对象,并在写帮手场可以做或感觉不正常。因此,调用getHelper()的线程可以看到对辅助对象的非空引用,但是请参阅辅助对象的字段的默认值,而不是构造函数中设置的值。

如果编译器内联对构造函数的调用,那么如果编译器可以证明构造函数不能抛出异常或执行同步,则可以自由地重新排序初始化对象和写入辅助对象字段的写入。

即使编译器没有重新排序这些写入,在多处理器上,处理器或内存系统也可能重新排序这些写入,正如在另一个处理器上运行的线程所感知的那样。

Doug Lea撰写了更详细的基于编译器的重新排序的描述。

一个测试用例显示它不起作用

保罗·雅库比克发现了一个使用双重检查锁定的例子,但没有正常工作。此处提供了该代码的略微清理版本。

在使用Symantec JIT的系统上运行时,它不起作用。特别是Symantec JIT编译

singletons [i] .reference = new Singleton();

以下(注意Symantec JIT使用基于句柄的对象分配系统)。

0206106A mov eax,0F97E78h
0206106F拨打01F6B210; 为...分配空间
                                                 ; 单身,返回结果在eax
02061074 mov dword ptr [ebp],eax; EBP是&singletons [i] .reference
                                                ; 在这里存储未构造的对象。
02061077 mov ecx,dword ptr [eax]; 取消引用句柄
                                                 ; 得到原始指针
02061079 mov dword ptr [ecx],100h; 接下来是4行
0206107F mov dword ptr [ecx + 4],200h; Singleton的内联构造函数
02061086 mov dword ptr [ecx + 8],400h
0206108D mov dword ptr [ecx + 0Ch],0F84030h

如您所见,在 调用Singleton的构造函数之前执行对singletons [i] .reference的赋值。这在现有的Java内存模型下是完全合法的,并且在C和C ++中也是合法的(因为它们都没有内存模型)。

修复不起作用

鉴于上述解释,许多人建议使用以下代码:

 

//(仍然)破碎的多线程版本
//“Double-Checked Locking”成语
class Foo { 
  private Helper helper = null;
  public Helper getHelper(){
    if(helper == null){
      帮手h;
      synchronized(this){
        h =帮手;
        if(h == null) 
            synchronized(this){
              h =新助手();
            } //释放内部同步锁
        帮手= h;
        } 
      }    
    回报助手;
    }
  //其他功能和成员......
  }

此代码将Helper对象的构造放在内部同步块中。这里的直观想法是,在释放同步的位置应该有一个内存屏障,这应该可以防止重新排序Helper对象的初始化和对字段助手的赋值。

不幸的是,这种直觉绝对是错误的。同步规则不起作用。monitorexit(即释放同步)的规则是必须在监视器释放之前执行monitorexit之前的操作。但是,没有规则说明在监视器发布之前可能无法执行monitorexit之后的操作。编译器移动赋值helper = h是完全合理和合法的; 在synchronized块中,在这种情况下,我们回到了之前的位置。许多处理器提供执行此类单向内存屏障的指令。将语义更改为要求将锁定释放为完整内存屏障会产生性能损失。

更多不适用的修复程序

您可以采取一些措施来强制编写器执行完全双向内存屏障。这是严重的,低效的,并且几乎保证在修改Java内存模型后不起作用。不要用这个。为了科学的利益,我在一个单独的页面上对这种技术进行了描述。不要使用它。

但是,即使初始化辅助对象的线程执行完整的内存屏障,它仍然不起作用。

问题是在某些系统上,看到辅助字段的非空值的线程也需要执行内存屏障。

为什么?因为处理器有自己的本地缓存内存副本。在一些处理器上,除非处理器执行高速缓存一致性指令(例如,存储器屏障),否则可以从陈旧的本地高速缓存的副本执行读取,即使其他处理器使用存储器屏障来强制其写入全局存储器。

我已经创建了一个单独的网页,讨论了如何在Alpha处理器上实际发生这种情况。

这值得吗?

对于大多数应用程序,简单地使getHelper() 方法同步的成本并不高。如果您知道它会导致应用程序的大量开销,那么您应该只考虑这种详细的优化。

通常,更高级别的聪明,例如使用内置mergesort而不是处理交换排序(请参阅SPECJVM DB基准测试)将产生更大的影响。

使它适用于静态单例

如果您正在创建的单例是静态的(即,只创建一个Helper),而不是另一个对象的属性(例如,每个Foo对象将有一个Helper,则有一个简单而优雅的解决方案。

只需将单例定义为单独类中的静态字段。Java的语义保证在引用字段之前不会初始化字段,并且访问该字段的任何线程都将看到初始化该字段所产生的所有写入。

 

class HelperSingleton {
  static Helper singleton = new Helper();
  }

它适用于32位原始值

虽然双重检查的锁定习惯用法不能用于对象的引用,但它可以用于32位原始值(例如,int或float)。请注意,它不适用于long或double,因为不保证64位原语的未同步读/写是原子的。

 

//为32位基元校正双重检查锁定
class Foo { 
  private int cachedHashCode = 0;
  public int hashCode(){
    int h = cachedHashCode;
    if(h == 0) 
    synchronized(this){
      if(cachedHashCode!= 0)返回cachedHashCode;
      h = computeHashCode();
      cachedHashCode = h;
      }
    返回h;
    }
  //其他功能和成员......
  }

实际上,假设computeHashCode函数总是返回相同的结果并且没有副作用(即幂等),您甚至可以摆脱所有同步。

 

//延迟初始化32位基元
//如果computeHashCode是幂等的,则是线程安全的
class Foo { 
  private int cachedHashCode = 0;
  public int hashCode(){
    int h = cachedHashCode;
    if(h == 0){
      h = computeHashCode();
      cachedHashCode = h;
      }
    返回h;
    }
  //其他功能和成员......
  }

使其与明确的内存障碍一起工作

如果您有明确的内存屏障指令,则可以使双重检查锁定模式工作。例如,如果您使用C ++编程,则可以使用Doug Schmidt等人的书中的代码:

 

//具有显式内存障碍的C ++实现
//应该可以在任何平台上工作,包括DEC Alphas
//来自“并发和分布式对象的模式”,
// Doug Schmidt撰写
template  TYPE *
Singleton  :: instance(void){
    //首先检查
    TYPE * tmp = instance_;
    //插入CPU特定的内存屏障指令
    //同步多处理器上的缓存行。
    asm(“memoryBarrier”);
    if(tmp == 0){
        //确保序列化(警卫
        //构造函数获取lock_)。
        守卫后卫(lock_);
        // 再检查一遍。
        tmp = instance_;
        if(tmp == 0){
                tmp = new TYPE;
                //插入CPU特定的内存屏障指令
                //同步多处理器上的缓存行。
                asm(“memoryBarrier”);
                instance_ = tmp;
        }
    返回tmp;
    }

使用线程本地存储修复双重检查锁定

Alexander Terekhov([email protected])提出了使用线程本地存储实现双重检查锁定的聪明建议。每个线程都保留一个线程本地标志,以确定该线程是否已完成所需的同步。

  class Foo {
	 / **如果perThreadInstance.get()返回一个非null值,则此线程
		已完成查看初始化所需的同步
		帮手* /
         private final ThreadLocal perThreadInstance = new ThreadLocal();
         private Helper helper = null;
         public Helper getHelper(){
             if(perThreadInstance.get()== null)createHelper();
             回报助手;
         }
         private final void createHelper(){
             synchronized(this){
                 if(helper == null)
                     helper = new Helper();
             }
	     //任何非null值都可以作为参数
             perThreadInstance.set(perThreadInstance);
         }
	}

这种技术的性能在很大程度上取决于您拥有的JDK实现。在Sun的1.2实现中,ThreadLocal非常慢。它们在1.3中明显更快,预计在1.4中更快。Doug Lea分析了实现延迟初始化的一些技术的性能。

在新的Java内存模型下

从JDK5开始,有一个新的Java内存模型和线程规范。

使用易失性修复双重检查锁定

JDK5和更高版本扩展了volatile的语义,以便系统不允许对任何先前的读或写进行重新排序的volatile的写入,并且对于任何后续的读或写,不能重新排序volatile的读取。有关详细信息,请参阅 Jeremy Manson博客中的此条目。

通过这种改变,可以通过声明辅助字段是易失性来使双重锁定成语工作。这不起作用 JDK4和更早版本下。

 

//使用volatile的获取/释放语义
//在volatile的当前语义下断开
  class Foo {
        private volatile Helper helper = null;
        public Helper getHelper(){
            if(helper == null){
                synchronized(this){
                    if(helper == null)
                        helper = new Helper();
                }
            }
            回报助手;
        }
    }

双重检查锁定不可变对象

如果Helper是一个不可变对象,使得Helper的所有字段都是最终的,那么双重检查锁定将无需使用volatile字段即可工作。我们的想法是对不可变对象(如String或Integer)的引用应该与int或float的行为方式大致相同; 读取和写入对不可变对象的引用是原子的。

复述成语的描述

  • Reality Check,Douglas C. Schmidt,C ++ Report,SIGS,Vol。1996年3月8日第3号。
  • Double-Checked Locking:一种有效初始化和访问线程安全对象的优化模式,Douglas Schmidt和Tim Harrison。 1996年 第三届年度模式语言程序设计会议
  • 懒惰的实例化,Philip Bishop和Nigel Warren,JavaWorld杂志
  • 在现实世界中编写Java线程,第7部分,Allen Holub,Javaworld Magazine,1999年4月。
  • Java 2 Performance and Idiom Guide,Craig Larman和Rhett Guthrie,p100。
  • 实践中的Java:有效Java的设计风格和习语,Nigel Warren和Philip Bishop,第142页。
  • 规则99,Java风格的元素,Allan Vermeulen,Scott Ambler,Greg Bumgardner,Eldon Metz,Trvor Misfeldt,Jim Shur,Patrick Thompson,SIGS参考图书馆
  • 使用Singleton模式的Java中的全局变量,Wiebe de Jong,Gamelan

你可能感兴趣的:(java·未分类,java并发编程,源码,jvm虚拟机)