colorful

zc qq:1337220912

 

Double-checked locking真的有效吗?

Double-checked locking真的有效吗?

作者: zsxwing 日期: 2011-04-29 10:48:06

在很多设计模式的书籍中,我们都可以看到类似下面的单例模式的实现代码,一般称为Double-checked locking(DCL)

01public class Singleton {
02 
03    private static Singleton instance;
04 
05    private Singleton() {
06        // do something
07    }
08 
09    public static Singleton getInstance() {
10        if (instance == null) {//1
11            synchronized (Singleton.class) {//2
12                if (instance == null) {//3
13                    instance = new Singleton();//4
14                }
15            }
16        }
17        return instance;
18    }
19}

这样子的代码看起来很完美,可以解决instance的延迟初始化。只是,事实往往不是如此。

问题在于instance = new Singleton();这行代码。

在我们看来,这行代码的意义大概是下面这样子的

 

 

	mem = allocate();             //收集内存 
ctorSingleton(mem); //调用构造函数
instance = mem; //把地址传给instance
	

 

这行代码在Java虚拟机(JVM)看来,却可能是下面的三个步骤(乱序执行的机制):

 

	mem = allocate();             //收集内存 
instance = mem; //把地址传给instance
	ctorSingleton(instance);      //调用构造函数

 

下面我们来假设一个场景。

  1. 线程A调用getInstance函数并且执行到//4。但是线程A只执行到赋值语句,还没有调用构造函数。此时,instance已经不是null了,但是对象还没有初始化。
  2. 很不幸线程A这时正好被挂起。
  3. 线程B获得执行的权力,然后也开始调用getInstance。线程B在//1发现instance已经不是null了,于是就返回对象了,但是这个对象还没有初始化,于是对这个对象进行操作就出错了。

问题就出在instance被提前初始化了。

解决方案一,不使用延迟加载:

01public class Singleton {
02 
03    private static Singleton instance = new Singleton();
04 
05    private Singleton() {
06        // do something
07    }
08 
09    public static Singleton getInstance() {
10        return instance;
11    }
12}

JVM内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的。这样当我们第一次调用getInstance的时候,JVM能够帮我们保证instance只被创建一次,并且会保证把赋值给instance的内存初始化完毕。

解决方案二,利用一个内部类来实现延迟加载:

01public class Singleton {
02 
03    private Singleton() {
04        // do something
05    }
06 
07    private static class SingletonContainer {
08        private static Singleton instance = new Singleton();
09    }
10 
11    public static Singleton getInstance() {
12        return SingletonContainer.instance;
13    }
14}

这两种方案都是利用了JVM的类加载机制的互斥。

方案二的延迟加载实现是因为,只有在第一次调用Singleton.getInstance()函数时,JVM才会去加载SingletonContainer,并且初始化instance。

不只Java存在这个问题,C/C++由于CPU的乱序执行机制,也同样存在这样的问题。

抱歉,我之前的理解有误,DCL在Java中失效的原因是JIT比较激进的优化导致的,在C/C++并不会由于CPU的乱序执行(调用构造函数和赋值这两个操作对CPU来说绝对不会乱序的)产生这个问题。

暂时不知道Java对于这个问题是否修复了。

posted on 2012-03-31 16:53 多彩人生 阅读(297) 评论(0)  编辑 收藏 引用


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


导航

统计

常用链接

留言簿(3)

随笔分类

随笔档案

搜索

最新评论

阅读排行榜

评论排行榜