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

已经有众多文章讨论 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;
}
}
 

 

Java Double-Checked LockingC++?  double-checked locking 模式在 Java 下面无法正常工作,这里先简要的总结一下。  根本原因在于 Java 的 memory model 允许所谓的 out-of-order write ,对于下面的 Java 代码,out-of-order write 可能导致灾难性的结果  public static Singleton getInstance()  {   if ( 阅读详情

相关推荐

线程同步之利器(2)——区域锁(Scoped locking)

区域锁(Scoped locking)不是一种锁的类型,而是一种锁的使用模式(pattern)。实际上是RAII模式在锁上面的具体应用。本文具体介绍了一下区域锁的实现。

邹鑫的专栏 1万+

双重检查锁(double-checked locking

在软件工程中,双重检查锁(double-checked locking[1])是一种设计模式,通过在加锁前检查锁标志(criterion),可以减少加锁的开销。只有当标志显示需要的时候才...

felix021的专栏 4329

双重检查锁(Double-Checked Locking)的缺陷

双重检查锁(Double-Checked Locking)被广泛用于多线程环境下实现延迟初始化实例,但是它其实是有缺陷的。

极客星云-CSDN博客 6753

【译】可以不要再使用Double-Checked Locking

Double-Checked Locking方法被广泛的使用于实现多线程环境下单例模式的懒加载方式实现,不幸的是,在JAVA中,这种方式有可能不能够正常工作。在其他语言环境中,如C++,依赖于处理器的内存模型、编译器的重排序以及编译器和同步库之间的工作方式。由于这些问题在C++中并不确定,因此...

471

Double checked locking,两次检查的锁

最开始知道这个内容是在和室友聊天的时候发现的,它说国内某互联网公司中有一道笔试题,需要当场写出无锁队列的代码。当时对于这个概念还是一知半解,后来他说陈皓博客上有相关的内容,本着好学的精神就去一窥究竟:无锁队列的实现 基本思想是利用编译器或者语言提供的原子操作,CAS(compare and swap或者compare and set)。 bool compare-and-swap(Ad

Sands make life richful. 776

设计模式(十)Singleton模式和Double-Checked Locking模式

Singleton模式和Double-Checked Locking模式 Singleton模式 《设计模式》一书中的说法,Singleton模式的意图是:保证一个类仅有一个实例,并提供一个访问它的全局访问点。 Singleton模式 关键特征 意图 希望对象只有一个实例,但是没有控制对象实例化的全局对象。还希望确保所有实体使用该对象相同的实例,而无需将引用传给它们 问题 几个...

xiaowei_innocence的博客 455

可以不要再使用Double-Checked Locking

Double-Checked Locking方法被广泛的使用于实现多线程环境下单例模式的懒加载方式实现,不幸的是,在JAVA中,这种方式有可能不能够正常工作。在其他语言环境中,如C++,依赖于处理器的内存模型、编译器的重排序以及编译器和同步库之间的工作方式。由于这些问题在C++中并不确定,因此我们不能够确定具体的行为。但是在C++中显示的内存屏障是可以被用来让其正常工作的,而这些屏障在JAVA中又

横练技术 859

Singleton 单件模式及其变体 Double-Checked Locking 双重检查锁模式

Singleton 单件模式, 及其变体 Double-Checked Locking 双重检查锁模式, 都可以用于确保某个类只有一个对象实例化.两个模式的区别在于: Singleton 模式用于单线程程序中, 而 Double-Checked Locking 模式用于多线程应用程序. Singleton 模式的意图是: 保证一个类仅有一个实例, 并提供一个访问它的全局访问点. 工作原理: 用

qq_33364884的博客 453

c++ 单例 Double-checked Locking

Double-checked Locking 在学习C++11多线程的时候,会碰到一大堆概念,mutex, lock, atomic, memory model, memory barrier, lock-free等。要更好的理解,可以: 了解CPU的Memory Barriers机制(Paul McKenny的Memory Barriers: a Hardware V

cncnlg的专栏 2027

Double-Checked Locking is Fixed In C++11

Thedouble-checked locking pattern(DCLP) is a bit of a notorious case study inlock-free programming. Up until 2004, there was no safe way to impl...

cicangxin3389的博客 269
上一篇: 【Hibernate 面试问题】答案待续
下一篇: 移动硬盘RAW格式修复
Snoopy7713
博客等级 码龄21年 17粉丝 681原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值