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 ,尽管它未必具有可移植性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值