首页>java频道>java教程>正文
双检测锁定(DCL)和Singleton模式的问题

www.zige365.com 2010-6-7 11:44:48 点击:发送给好友 和学友门交流一下 收藏到我的会员中心

用JavaWorld上对应文章的标题来评论这种做法就是smart, but broken。来看原因:

Java编译器为了提高程序性能会进行指令调度,CPU在执行指令时同样出于性能会乱序执行(至少现在用的大多数通用处理器都是out-of-order的),另外cache的存在也会改变数据回写内存时的顺序[2]。JMM(Java Memory Model, 见[1])指出所有的这些优化都是允许的,只要运行结果和严格按顺序执行所得的结果一样即可。

Java假设每个线程都跑在自己的处理器上,享有自己的内存,和共享的主存交互。注意即使在单核上这种模型也是有意义的,考虑到cache和寄存器会保存部分临时变量。理论上每个线程修改自己的内存后,必须立即更新对应的主存内容。但是Java设计师们认为这种约束会影响程序性能,他们试着创造了一套让程序跑得更快、但又保证线程之间的交互与预期一致的内存模型。

synchronized关键字便是其中一把利器。事实上,synchronized块的实现和Linux中的信号量(semaphore)还是有区别的,前者过程中锁的获得和释放都会都会引发一次Memory Barrier来强制线程本地内存和主存之间的同步。通过这个机制,Java中的同步机制保证了synchronized块中指令的原子性(atomic)。

双检测锁定的问题

好了,回过头来看DCL问题。看起来访问一个未同步的instance字段不会产生什么问题,我们再次来假设一个场景:

线程一进入同步块,执行instance = new Singleton(); 线程二刚开始执行getResource();

按照顺序的话,接下来应该执行的步骤是 1) 分配新的Singleton对象的内存 2) 调用Singleton的构造器,初始化成员字段 3) instance被赋为指向新的对象的引用。

前面说过,编译器或处理器都为了提高性能都有可能进行指令的乱序执行,线程一的真正执行步骤可能是1) 分配内存 2) instance指向新对象 3) 初始化新实例。如果线程二在2完成后3执行前被唤醒,它看到了一个不为null的instance,跳出方法体走了,带着一个还没初始化的Singleton对象。

错误发生的一种情形就是这样,关于更详细的编译器指令调度导致的问题,可以参看这个网页 [4]。

[3] 中提供了一个编译器指令调度的证据

instance = new Singleton(); 这条命令在Symantec JIT中被编译成

  1. 0206106A   mov         eax,0F97E78h  
  2. 0206106F   call        01F6B210                  ; 分配空间  
  3. 02061074   mov         dword ptr [ebp],eax       ; EBP中保存了instance的地址   
  4.  
  5. 02061077   mov         ecx,dword ptr [eax]       ; 解引用,获得新的指针地址   
  6.  
  7. 02061079   mov         dword ptr [ecx],100h      ; 接下来四行是inline后的构造器  
  8. 0206107F   mov         dword ptr [ecx+4],200h      
  9. 02061086   mov         dword ptr [ecx+8],400h  
  10. 0206108D   mov         dword ptr [ecx+

    本新闻共3页,当前在第2页  1  2  3  

我要投稿 新闻来源: 编辑: 作者:
相关新闻
关于Smooks 1.2框架:处理XML与非XML的Java框架
浅谈在Java中使用Gmail发送邮件
关于Java三种常见异常及解决
编程基础:有关取精度,ToString和Math.Round
Java代码混淆器推荐