Your Ad Here
首页 | 编程语言 | 网站建设 | 游戏天堂 | 冲浪宝典 | 网络安全 | 操作系统 | 软件时空 | 硬件指南 | 病毒相关 | IT 认证
软讯网络 > 编程语言 > .NET > C#.NET > The Problem with Double-checked Locking (双检锁)
【标  题】:The Problem with Double-checked Locking (双检锁)
【关键字】:The,Problem,with,Double-checked,Locking
【来  源】:http://blog.joycode.com/demonfox/archive/2007/01/04/90894.aspx

The Problem with Double-checked Locking (双检锁)

Your Ad Here

前几天在看一段.NET源代码的时候偶尔遇到了Double-checked Locking (双检锁)的一个使用,于是想到了以前看过的一些资料,写出来分享一下。

主要参考:The "Double-Checked Locking is Broken" Delaration (http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html)

双检锁是在多线程环境下很常见的一种实现singleton模式里lazy initialization的方法。

先看一下最这个模式的起源(注:代码为Java,不过这个问题适用各种语言,比如C++):

// Single threaded version class Foo { private Helper helper = null; public Helper getHelper() { if (helper == null) helper = new Helper(); return helper; } // other functions and members... }

很容易看出,在多线程的情况下,上面的getHelper是不能正确工作的(可能生成多个helper实体)。

于是有下面的改进代码:

// Correct multithreaded version class Foo { private Helper helper = null; public synchronized Helper getHelper() { if (helper == null) helper = new Helper(); return helper; } // other functions and members... }

这样写程序不会出错,因为整个getHelper是一个整体的"critical section",但就是效率很不好,因为我们的目的其实只是在第一个初始化helper的时候需要locking(加锁),而后面取用helper的时候,根本不需要线程同步。

于是聪明的人们想出了下面的做法:

// Broken multithreaded version // "Double-Checked Locking" idiom class Foo { private Helper helper = null; public Helper getHelper() { if (helper == null) synchronized(this) { if (helper == null) helper = new Helper(); } return helper; } // other functions and members... }

思路很简单,就是我们只需要同步(synchronize)初始化helper的那部分代码从而使代码既正确又很有效率。

这就是所谓的“双检锁”机制(顾名思义)。

很可惜,这样的写法在很多平台和优化编译器上是错误的。

原因在于:helper = new Helper()这行代码在不同编译器上的行为是无法预知的。一个优化编译器可以合法地如下实现helper = new Helper():

1. helper = 给新的实体分配内存

2. 调用helper的构造函数来初始化helper的成员变量

现在想象一下有线程A和B在调用getHelper,线程A先进入,在执行到步骤1的时候被踢出了cpu。然后线程B进入,B看到的是helper已经不是null了(内存已经分配),于是它开始放心地使用helper,但这个是错误的,因为在这一时刻,helper的成员变量还都是缺省值,A还没有来得及执行步骤2来完成helper的初始化。

当然编译器也可以这样实现:

1. temp = 分配内存

2. 调用temp的构造函数

3. helper = temp

如果编译器的行为是这样的话我们似乎就没有问题了,但事实却不是那么简单,因为我们无法知道某个编译器具体是怎么做的,因为在Java的memory model里对这个问题没有定义(C++也一样),而事实上有很多编译器都是用第一种方法(比如symantec的just-in-time compiler),因为第一种方法看起来更自然。

在上面的参考文章中还提到了更复杂的修改方法,不过很可惜,都是错误的,我这里就略去不介绍了。

那么有什么解决方案呢?有如下一些:

1. 如果你的singleton是static的,那你可以将这个singleton申明为一个独立类的一个成员变量:

class HelperSingleton { static Helper singleton = new Helper(); }

Java的语意会保证:1. lazy initialization, 2. singleton在被调用前已经完全初始化了。

2. 双检锁对于基础类型(比如int)适用。很显然吧,因为基础类型没有调用构造函数这一步。事实上,我前面提到的.NET里面的那段代码就是在一个int变量上使用双检锁。

3. 使用explicit memory barrier。这个我不说了,关于memory barrier我们可以写一本小册子来介绍,有兴趣的朋友可以自己查一下资料,上面的参考里也有很多相关链接。

4. 使用Thread Local Storage。也不介绍了。

上面的文章还提到了Java在考虑为volatile关键字定义新的语意来解决这个问题以及双检锁对Java里immutable对象影响,不过因为这篇文章已经有些年头而我也不是Java的专家,所以不太清楚现在的情况怎样,总之,在遇到双检锁的时候,需要的朋友应该做些必要的调查来确定自己的代码是线程安全的。

 

Update: 这篇文章也很浅显有用 -- Implementing the Singleton Pattern in C#

             .NET中DCL的处理 -- http://discuss.develop.com/archives/wa.exe?A2=ind0203B&L=DOTNET&P=R375 (感谢saucer提供)

几个VS 2005 SP1连接和一些有用的信息:【上一篇】
MVP的申请批下来了:【下一篇】
【相关文章】
  • Mersenne Primes:History, Theorems and Lists
  • start with ... connect by用法简介 sql有向图问题期待新解决方案
  • connect by 和 start with
  • solaris 8 md5 password authentication
  • emacs with xft
  • RIP unicast without using Neighbor
  • 《The Complete Effect and HLSL Guide》翻译连载(十一)
  • ARGB8888 --> ARGB4444&Other Formats
  • 《The Complete Effect and HLSL Guide》翻译连载(十二)
  • How to change SYSDATE's value to a static date without changing system date
  • 【随机文章】
  • ip classes是干吗的
  • Java中的浮点数分析
  • 在Linux下装PHP+JSP
  • 设计模式学习笔记-Singleton
  • 对视图的操作
  • 夏桅荐《应用框架的设计与实现》
  • 一种函数的计算方法并给出相应的曲线图片
  • Java Portlet 工具:将 Java Web 应用程序转换为自适型--page2
  • [Architechture探索] 程序架构与源码重用 (C++)Page-001
  • 《.NET Compact Framework 移动开发指南》,即将与大家见面
  • 【相关评论】
    没有相关评论
    【发表评论】
    姓名:
    邮件:
    随机码*
    评论*
          
    |  首 页  |  版权声明  |  联系我们   |  网站地图  |
    CopyRight © 2004-2007 软讯网络 All Rigths Reserved.