`
snoopy7713
  • 浏览: 1148039 次
  • 性别: Icon_minigender_2
  • 来自: 火星郊区
博客专栏
Group-logo
OSGi
浏览量:0
社区版块
存档分类
最新评论

Java Double-Checked Locking 已死,C++ 呢?

    博客分类:
  • java
阅读更多

已经有众多文章讨论 double-checked locking 模式在 Java 下面无法正常工作,这里先简要的总结一下。

根本原因在于 Java 的 memory model 允许所谓的 out-of-order write ,对于下面的 Java 代码,out-of-order write 可能导致灾难性的结果

public static Singleton getInstance()

{

  if (instance == null)

  {

    synchronized(Singleton.class) {  //1

      if (instance == null)          //2

        instance = new Singleton();  //3

    }

  }

  return instance;

}
 

 

问题的起因在于语句 //3 ,JIT 所生成的汇编代码所作的事情并不是先生成一个 Singleton 对象,然后将其地址赋予 instance 。相反,它的做法是

1. 先申请一块空内存
2. 将其地址赋予 instance
3. 在 instance 所指的地址之上构建对象

下面的汇编代码提供了证明,说明这不只是一个脑筋急转弯,而是实际发生在 JIT 里面的。代码来自 Peter Haggar 的文章,我只是引用一下。

054D20B0   mov         eax,[049388C8]          ;load instance ref
054D20B5   test          eax,eax                         ;test for null
054D20B7   jne           054D20D7
054D20B9   mov         eax,14C0988h
054D20BE   call          503EF8F0                    ;allocate memory
054D20C3   mov         [049388C8],eax          ;store pointer in
                                                                              ;instance ref. instance 
                                                                              ;non-null and ctor
                                                                              ;has not run
054D20C8   mov         ecx,dword ptr [eax]
054D20CA   mov         dword ptr [ecx],1         ;inline ctor - inUse=true;
054D20D0   mov         dword ptr [ecx+4],5     ;inline ctor - val=5;
054D20D7   mov         ebx,dword ptr ds:[49388C8h]
054D20DD   jmp         054D20B0

其中地址为 054D20BE 的代码正在分配内存,而接下来的一行将其赋予 instance ,这个时候 Singleton 的构造函数根本就还没有被调用。

那 么问题在哪里?如果线程调度发生在 instance 已经被赋予一个内存地址,而 Singleton 的构造函数还没有被调用的微妙时刻,那么另一个进入此函数的线程会发觉 instance 已经不为 null ,从而放心大胆的将 instance 返回并使用之。但是这个可怜的线程并不知道此时 instance 还没有被初始化呢!

症结在于:首先,构造一个对象不是原子操作,而是可以被打断的;第二,更重要的,Java 允许在初始化之前就把对象的地址写回,这就是所谓 out-of-order 。

那么,对于 C++ 呢?典型的 C++ double-checked locking 可能是这样的
  

static Singleton* getInstDC()
    {
        if(inst_ == 0)
        {
            boost::mutex::scoped_lock l(guard_);
            if(inst_ == 0)
                inst_ = new Singleton();
        }
        return inst_;
    } 

 

正 如 Java 的行为取决于 JIT 的处理方式,C++ 程序的行为要由编译器来决定。如果某个编译器的处理与 JIT 类似,那么 C++ 程序员也只好对 double-checked locking 说再见。下面是 VC7.1 在 release 配置下生成的代码:

    static Singleton* getInstDC()
    {
00401110  mov         eax,dword ptr fs:[00000000h]
00401116  push        0FFFFFFFFh
00401118  push        offset __ehhandler$?getInstDC@Singleton@@SAPAV1@XZ (4095F8h)
0040111D  push        eax 
        if(inst_ == 0)
0040111E  mov         eax,dword ptr [Singleton::inst_ (40D000h)]
00401123  mov         dword ptr fs:[0],esp
0040112A  sub         esp,8
0040112D  test        eax,eax
0040112F  jne         Singleton::getInstDC+6Eh (40117Eh)
        {
            boost::mutex::scoped_lock l(guard_);
00401131  mov         ecx,offset Singleton::guard_ (40D004h)
00401136  mov         dword ptr [esp],offset Singleton::guard_ (40D004h)
0040113D  call        boost::mutex::do_lock (401340h)
00401142  mov         byte ptr [esp+4],1
            if(inst_ == 0)
00401147  mov         eax,dword ptr [Singleton::inst_ (40D000h)]
0040114C  test        eax,eax
0040114E  mov         dword ptr [esp+10h],0
00401156  jne         Singleton::getInstDC+57h (401167h)
                inst_ = new Singleton();
00401158  push        1   
0040115A  call        operator new (4011A2h)
0040115F  add         esp,4
00401162  mov         dword ptr [Singleton::inst_ (40D000h)],eax
        }
00401167  mov         ecx,offset Singleton::guard_ (40D004h)
0040116C  mov         dword ptr [esp+10h],0FFFFFFFFh
00401174  call        boost::mutex::do_unlock (401360h)
        return inst_;
00401179  mov         eax,dword ptr [Singleton::inst_ (40D000h)]
    }
0040117E  mov         ecx,dword ptr [esp+8]
00401182  mov         dword ptr fs:[0],ecx
00401189  add         esp,14h
0040118C  ret              

从标记为红色的那一句,我们看到了希望:对 inst_ 的赋值发生在 new 完成之后,这意味着至少在 VC7.1 中,我们尚且可以放心使用 double-checked locking ,尽管它未必具有可移植性。

 

一个替代方法:

class SingletonTest
{
private static SingletonTest instance;

private SingletonTest
{
}

private static class Instance//嵌套类只加载一次。
{
static fianl SingletonTest Instance=new SingletonTest();
}

public static SingletonTest()
{
return Instance.instance;
}
}
 

 

分享到:
评论

相关推荐

    C++ and the Perils of Double-Checked Locking

    在介绍双检锁模式(Double-Checked Locking Pattern,DCLP)的C++实现中,Scott Meyers和Andrei Alexandrescu在其2004年的文章中指出,传统的单例模式实现并不具备线程安全性。单例模式是设计模式中经常被提及的一种...

    C++ and the Perils of Double Checked Locking.zip

    《C++ and the Perils of Double Checked Locking》是一篇探讨C++编程中双重检查锁定(Double-Checked Locking)模式潜在问题的文献。在多线程编程中,双重检查锁定是一种常见的优化策略,旨在减少对同步原语的依赖...

    c++ and Peris of Double Checked Locking

    标题:C++与双检查锁定(Double Checked Locking)的陷阱 描述:C++如何解决单例模式的线程安全问题 ### 关键知识点解析: #### 单例模式的线程安全挑战 单例模式是一种设计模式,确保一个类只有一个实例,并提供...

    C++CLI中实现singleton模式

    双重检测锁(Double-Checked Locking)实现的Singleton模式在多线程应用中有相当的价值。在ACE的实现中就大量使用ACE_Singleton模板类将普通类转换成具有Singleton行为的类。这种方式很好地消除了一些重复代码臭味,...

    ACE中的DoubleCheckedLocking模式

    北京火龙果软件工程技术中心意图无论什么时候当临界区中的代码仅仅需要加锁一次,同时当其获取锁的时候必须是线程安全的,可以用DoubleCheckedLocking模式来减少竞争和加锁载荷。动机1、标准的单例。开发正确的有效...

    设计模式C++ 04-05

    在C++中实现单例,需要注意线程安全问题,可能会使用到静态成员变量、双重检查锁定(Double-Checked Locking)以及C++11的std::call_once和std::once_flag等工具来确保正确性。 第5章可能会讲解“工厂模式”...

    java 反模式 卷1 pdf

    例如,对于"Lazy Initialization"反模式,书中可能会介绍双重检查锁定(Double-Checked Locking)的原始实现及其存在的问题,然后提出基于 volatile 关键字的改进方案,以确保线程安全的延迟初始化。 同时,附带的...

    singleton单例模式

    2. **双重检查锁定(Double-Checked Locking)**:这种方法是在多线程环境下保证线程安全的单例。首先检查是否已经实例化,如果未实例化,则同步锁定构造函数,确保只有一个线程可以进入,然后创建实例。这种方式...

    设计模式C++学习之单例模式(Singleton)

    为了解决这个问题,可以采用双重检查锁定(Double-Checked Locking)的策略: ```cpp class Singleton { private: Singleton() {} static Singleton* instance; static std::mutex mtx; public: static ...

    设计模式一点通之单态模式 C++

    3. **双检锁/双重校验锁定(Double-checked Locking)**: 这种方式在多线程环境下性能较好,只有在首次创建实例时才进行同步。 ```cpp class Singleton { private: Singleton() {} volatile static bool ...

    SingletonText.rar_系统设计方案_C/C++_

    描述提到“单件技术模板里面提供了4种方案”,这可能包括经典的懒汉式(Lazy Initialization)、饿汉式(Eager Initialization)、双检锁/双重检查锁定(Double-checked Locking)以及静态内部类(Static Inner ...

    C++设计模式源码

    在C++中,单例模式可以通过静态成员、枚举或双检锁(double-checked locking)来实现。在Qt中,QCoreApplication就是一个单例类的例子。 2. **工厂模式**:提供一个接口用于创建一系列相关或相互依赖的对象,而无需...

    Java的Singleton模式代码(免资源分)

    #### 三、双重检查锁定(Double-Checked Locking) **代码示例:** ```java public class Singleton { private volatile static Singleton instance; private Singleton() {} public static Singleton ...

    C++完美实现Singleton模式

    为了解决这一问题,可以采用双重检查锁定(Double-Checked Locking)模式来确保线程安全性: ```cpp class Singleton { public: static Singleton* Instance() { if (_instance == nullptr) { lock_guard<mutex>...

    Toou-2D-master.zip

    在C++中实现单例设计模式有多种方法,常见的包括懒汉式(lazy initialization)、饿汉式(eager initialization)以及双重检查锁定(double-checked locking)等。懒汉式在第一次使用时才创建单例,饿汉式在程序启动...

    一例读懂设计模式-单例模式、简单工厂模式.zip

    - 双重检查锁定(Double-Checked Locking):在多线程环境下,确保单例在被多次请求时仍保持唯一性,同时减少同步开销。 - 饿汉式(静态常量):在类加载时立即创建实例,保证线程安全,但可能会浪费内存资源。 -...

    单件实现范例

    - 双重检查锁定(Double-Checked Locking):在多线程环境中,只有在真正需要创建单例时才进行同步操作,减少锁的使用提高性能。 - 静态内部类或枚举:利用Java类加载机制来保证线程安全,是推荐的实现方式。 5. ...

    Singleton模式源程序

    2. 双重检查锁定(Double-Checked Locking):这是最常见的一种线程安全实现方式。在获取实例时先检查实例是否已经存在,若不存在再加锁并创建。这样可以减少不必要的同步开销。 3. 原子操作:利用C++11引入的std::...

    介绍设计模式的代码

    它的实现通常采用双重检查锁定(Double-Checked Locking)或静态内部类方式。工厂方法模式则通过抽象工厂接口来创建对象,使得具体创建哪个类的对象可以在运行时决定。建造者模式用于构建复杂对象,通过一步一步构建...

    JAVA程序员面试32问.doc

    - 单例模式确保一个类只有一个实例,通常使用双重检查锁定(Double-Checked Locking)或静态内部类方式实现。 这些面试问题覆盖了Java的基础语法、面向对象、集合框架、并发编程等多个方面,是Java程序员面试中...

Global site tag (gtag.js) - Google Analytics