何谓双重检查成例?

来源:互联网 发布:身份证读卡器端口失败 编辑:程序博客网 时间:2024/06/10 07:49

最近在忙考试的事,笔记做的少了。
在图书馆看到了阎宏博士的《java与模式》,里面在讲单例模式时谈到了双重检查成例,以前没听过。先看下面的代码:
 

Code:
  1. public class LazySingleton{   
  2.     private static LazySingleton m_instance = null;   
  3.   
  4.     private LazySingleton() {   
  5.     }   
  6.   
  7.     public static LazySingleton getInstance() {   
  8.         if (m_instance == null) {   
  9.             m_instance = new LazySingleton();   
  10.         }   
  11.         return m_instance;   
  12.     }   
  13. }  

这称为赖汉式单例,初一看似乎没什么问题,但实际运用中就会发生情况。我们知道一般在J2EE环境中都是多线程的,假设当m_instance==null的瞬间,有两个线程A和B同时进入getInstance()方法内,这时就会产生两个实例。虽然最后m_instance只会引用其中的一个,但A和B已经分别获得并引用了这两个实例之一,显然这违背了单例的初衷。这时我们就会想到将getInstance()方法同步,修改后的代码如下:

Code:
  1. public class LazySingleton{   
  2.     private static LazySingleton m_instance = null;   
  3.   
  4.     private LazySingleton() {   
  5.     }   
  6.   
  7.     public static LazySingleton getInstance() {   
  8.         synchronized (LazySingleton.class) {   
  9.             if (m_instance == null) {   
  10.                 m_instance = new LazySingleton();   
  11.             }   
  12.         }   
  13.         return m_instance;   
  14.     }   
  15. }  

这样就没有问题了。但还是有人会问,方法同步后会对性能造成一定影响,有没有什么办法再优化一下?
于是就有了下面这样的代码:
 

Code:
  1. public class LazySingleton{   
  2.     private static LazySingleton m_instance = null;   
  3.   
  4.     private LazySingleton() {   
  5.     }   
  6.     public static LazySingleton getInstance() {   
  7.         if (m_instance == null) {   
  8.             synchronized (LazySingleton.class) {   
  9.                 if (m_instance == null) {   
  10.                     m_instance = new LazySingleton();   
  11.                 }   
  12.             }   
  13.         }   
  14.         return m_instance;   
  15.     }   
  16. }  

这就是所谓的双重检查成例了。在同步代码外再进行一次检查,当LazySingleton已存在实例的时候,就不会进入同步代码块内,从而就解决了同步带来的性能问题。这个想法非常好,而且在C/C++里也可以用得很欢。然而不幸的是,在Java里却不行。为什么?

阎宏博士是这样解释的:在Java 编译器中,LazySingleton 类的初始化与m_instance 变量赋值的顺序不可预料。如果一个线程在没有同步化的条件下读取m_instance 引用,并调用这个对象的方法的话,可能会发现对象的初始化过程尚未完成,从而造成崩溃。

这两句话不太好理解,但“这个结论已成铁案,根本原因在于JVM的类、变量初始化机制”。java允许jvm在不影响单线程的执行语义的情况下,改变语句的执行顺序。

这里有关于双重检查成例的讨论:http://faq.csdn.net/read/184228.html

原创粉丝点击