双重检查锁定模式

双重检查锁定模式（也被称为"双重检查加锁优化"，"锁暗示"（Lock hint）) 是一种软件设计模式用来减少并发系统中竞争和同步的开销。双重检查锁定模式首先验证锁定条件(第一次检查)，只有通过锁定条件验证才真正的进行加锁逻辑并再次验证条件(第二次检查)。
该模式在某些语言在某些硬件平台的实现可能是不安全的。有的时候，这一模式被看做是反模式。
它通常用于减少加锁开销，尤其是为多线程环境中的单例模式实现“惰性初始化”。惰性初始化的意思是直到第一次访问时才初始化它的值。

**Java中的使用**
考虑下面的Java代码date=20120306104259 
// Single threaded version
class Foo {
private Helper helper = null;
public Helper getHelper() {
if (helper == null) {
helper = new Helper();
}
return helper;
}
// other functions and members...
}
这段在使用多线程的情况下无法正常工作。在多个线程同时调用getHelper()时，必须要获取锁，否则，这些线程可能同时去创建对象，或者某个线程会得到一个未完全初始化的对象。
锁可以通过代价很高的同步来获得，就像下面的例子一样。
// Correct but possibly expensive multithreaded version
class Foo {
private Helper helper = null;
public synchronized Helper getHelper() {
if (helper == null) {
helper = new Helper();
}
return helper;
}
// other functions and members...
}
只有getHelper()的第一次调用需要同步创建对象，创建之后getHelper()只是简单的返回成员变量，而这里是无需同步的。
由于同步一个方法会降低100倍或更高的性能， 每次调用获取和释放锁的开销似乎是可以避免的：一旦初始化完成，获取和释放锁就显得很不必要。许多程序员以下面这种方式进行优化：
# 检查变量是否被初始化(不去获得锁)，如果已被初始化立即返回这个变量。
# 获取锁
# 第二次检查变量是否已经被初始化：如果其他线程曾获取过锁，那幺变量已被初始化，返回初始化的变量。
# 否则，初始化并返回变量。
// 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...
}
直觉上，这个算法看起来像是该问题的有效解决方案。然而，这一技术还有许多需要避免的细微问题。例如，考虑下面的事件序列：
# 线程A发现变量没有被初始化, 然后它获取锁并开始变量的初始化。
# 由于某些编程语言的语义，编译器生成的代码允许在线程A执行完变量的初始化之前，更新变量并将其指向部分初始化的对象。
# 线程B发现共享变量已经被初始化，并返回变量。由于线程B确信变量已被初始化，它没有获取锁。如果在A完成初始化之前共享变量对B可见（这是由于A没有完成初始化或者因为一些初始化的值还没有覆盖B使用的内存(缓存一致性)），程序很可能会崩溃。
在J2SE 1.4或更早的版本中使用双重检查锁有潜在的危险，有时会正常工作：区分正确实现和有小问题的实现是很困难的。取决于编译器，线程的调度和其他并发系统活动，不正确的实现双重检查锁导致的异常结果可能会间歇性出现。重现异常是十分困难的。
在J2SE 5.0中，这一问题被修正了。volatile关键字保证多个线程可以正确处理单件实例。date=20120306104259 描述了这一新的语言特性:
// Works with acquire/release semantics for volatile
// Broken under Java 1.4 and earlier semantics for volatile
class Foo {
private volatile Helper helper = null;
public Helper getHelper() {
Helper result = helper;
if (result == null) {
synchronized(this) {
result = helper;
if (result == null) {
helper = result = new Helper();
}
}
}
return result;
}
// other functions and members...
}
注意局部变量result的使用看起来是不必要的。对于某些版本的Java虚拟机，这会使代码提速25%，而对其他的版本则无关痛痒。
如果helper对象是静态的(每个类只有一个), 可以使用双重检查锁的替代模式惰性初始化模式。查看 上的列表16.6。
// Correct lazy initialization in Java
@ThreadSafe
class Foo {
private static class HelperHolder {
public static Helper helper = new Helper();
}
public static Helper getHelper() {
return HelperHolder.helper;
}
}
这是因为内部类直到他们被引用时才会加载。
Java 5中的final语义可以不使用volatile关键字实现安全的创建对象：
public class FinalWrapper {
public final T value;
public FinalWrapper(T value) {
this.value = value;
}
}
public class Foo {
private FinalWrapper helperWrapper = null;
public Helper getHelper() {
FinalWrapper wrapper = helperWrapper;
if (wrapper == null) {
synchronized(this) {
if (helperWrapper == null) {
helperWrapper = new FinalWrapper(new Helper());
}
wrapper = helperWrapper;
}
}
return wrapper.value;
}
}
为了正确性，局部变量wrapper是必须的。这一实现的性能不一定比使用volatile的性能更高。

**Microsoft Visual C++ 中的使用**
如果指针是由C++关键字volatile定义的，那幺双重检查锁可以在Visual C++ 2005 或更高版本中实现。Visual C++ 2005 保证volatile变量是一种内存屏障，阻止编译器和CPU重新安排读入和写出语义。 在先前版本的Visual C++则没有此类保证。在其他方面将指针定义为volatile可能会影响程序的性能。例如，如果指针定义对代码的其他地方可见，强制编译器将指针视为屏障，就会降低程序的性能，这是完全不必要的。

**参见**
测试和测试并设置idiom作为底层加锁机制。
*按需初始化持有者作为Java中线程安全的替代者。

**参考资料**

**外部链接**
 Issues with the double checked locking mechanism captured in  Jeu George's Blogs  Pure Virtuals- Implementation of Various Singleton Patterns including the  Double Checked Locking Mechanismdate=20120204211315  at  TEKPOOLdate=20120204210751 - "Double Checked Locking" Description from the Portland Pattern Repository- "Double Checked Locking is Broken" Description from the Portland Pattern Repository- Paper " C++ and the Perils of Double-Checked Lockingdate=20120207111747 " (475 KB) by Scott Meyers and Andrei Alexandrescu- Article " Double-checked locking: Clever, but broken" by Brian Goetz-  The "Double-Checked Locking is Broken" Declarationdate=20120306104259 ; David Bacon et al.-  Double-checked locking and the Singleton patterndate=20050626074921 -  Singleton Pattern and Thread Safety-  volatile keyword in VC++ 2005date=20080417193529 -  Java Examples and timing of double check locking solutions