java 双重检查模式 在并发环境下 兼顾安全和效率
成例(Idiom)是一种代码层次上的模式,是在比设计模式的层次更具体的层次上的代码技巧。成例往往与编程语言密切相关。
双重检查成例(Double Check Idiom)是从C语言移植过来 的一种代码模式。
先看一个例子:
class Foo {
private Helper helper = null;
public Helper getHelper() {
if (helper == null) {
helper = new Helper();
}
return helper;
}
}
写出这样的代码,本意显然是要保持在整个JVM中只有一个Helper的实例。但非常明显的是,如果在多线程的环境中运行,上面的代码可能会有两个甚至两个以上的Helper对象被创建出来,从而造成错误。为了克服没有线程安全的缺点,下面给出一个线程安全的例子:
class Foo {
private Helper helper = null;
public
synchronized Helper getHelper() {
if (helper == null) {
helper = new Helper();
}
return helper;
}
}
显然,由于整个静态工厂方法都是同步化的,因此,不会有两个线程同时进入这个方法。但是,仔细审查上面的方法会发现,同步化实际上只在helper变量第一次被赋值之前才有用。在helper变量有了值以后,同步化实际上变成了一个不必要的瓶颈。如果能有一个方法去掉这个小小的额外开销,不是更加完美了吗?因此,就有了下面这个设计“巧妙”的
双重检查成例。
class Foo {
private Helper helper = null;
public Helper getHelper() {
if (helper == null) {
synchronized(this) {
if (help == null) {
helper = new Helper();
}
}
}
return helper;
}
}
可以看到,在上面的方法中,同步化仅用来避免多个线程同时初始化这个类,而不是同时调用这个静态工厂方法。如果这是正确的,那么使用这一个成例之后,“懒汉式”单例类就可以摆脱掉同步化瓶颈,达到一个很妙的境界,如下述代码:
public class LazySingleton {
private static LazySingleton m_instance = null;
private LazySingleton() { }
private static LazySingleton m_instance = null;
private LazySingleton() { }
public static LazySingleton getInstance() {
if (m_instance == null) {
synchronized(LazySingleton.class) {
if (m_instance == null) {
m_instance = new LazySingleton();
}
}
}
return m_instance;
}
if (m_instance == null) {
synchronized(LazySingleton.class) {
if (m_instance == null) {
m_instance = new LazySingleton();
}
}
}
return m_instance;
}
}
令人吃惊的是,在C语言里得到普遍应用的
双重检查成例在多数的Java语言编译器里面并不成立。上面使用了
双重检查成例的“懒汉式”单例类,不能工作的基本原因在于,在Java编译器中,
LazySingleton类的初始化与m_instance变量赋值的顺序不可预料。如果一个线程在没有同步化的条件下读取m_instance引用,并调用这个对象的方法的话,可能会发现对象的初始化过程尚未完成,从而造成崩溃。
一般而言,
双重检查成例对Java语言来说是不成立的。在一般情况下,使用饿汉式单例模式或者对整个静态工厂方法同步化的懒汉式单例模式足以解决在实际设计工作中遇到的问题。