我最早学java的时候,学的单例的写法是:
   写法1
      
   public class Test(){
     private static final Test instance = new Test();
     private Test(){
     }
     public Test getInstance(){
       return instance;
     }
   }
   
   这种写法在类初始化时就将instance赐值。后来又学到了一种线程安全的double check方法:
    写法2
        
   public class Test(){
     private static Test instance = null;
     private Test(){
     }
     public Test getInstance(){
       if (instance == null) {
         synchronized (UserDao.class) {
           if (instance == null) {
            instance = new Test();
           }
         }
       }
       return instance;
    }
  }
     
   这种写法的好处是保证了多线程下也只有一个Test实例。今天突然想到,就第一种写法来说,因为类是在第一次执行static方法时初始化(单例不能new所以肯定先执行static方法),而初始化类是线程安全的么?
  如果初始化类是线程安全的,那么private static final Test instance = new Test();instance只会初始化一次,只有一个new Test()对象,跟第二种写法相比,不考虑第一次instance赐值,常规来看每次比第二种方法少执行了一次判断:if(instance == null).那就没有任何使用第二种double check的单例的方法的理由。
  如果初始化类不是线程安全的,即多个线程会同时初始化Test类,那instance可能被赋值多次,即可能new多个Test对象,可能浪费一些堆区的内存。但即使是这样,评估一下,多个线程同时初始化Test类的instance变量导致new了多个对象的概率一般会相当低吧,跟第二种写法相比,少执行一次判断,整体来说是不是比有可能多占了一点内容性能上要好呢?更何况即使new了多个对象也只有一个被"可见"的引用,其他对象都可以正常被gc回收,所以说是不是第一种写法比double check的写法要好呢?

解决方案 »

  1.   

    楼主意识到的第一个问题,其实很好解决,因为,那个static后面的方法,是在类加载到虚拟机的时候就被调用的。
    那楼主查一下ClassLoader类的加载方法,是不是线程安全的,不就行了么 ? 如果,类加载器加载一个类的动作是线程安全的,初始化静态变量也包含在这一动作里,那不就没啥可疑的问题了么?两种方式,侧重点应该是是否采用lazy吧?(就是延迟还是不延迟的问题)本人才疏学浅,希望达人们批评指正,但,别骂我啊~
      

  2.   

    看不懂第2种以及楼主解释!!!有double check这样的说法嘛??研究中
      

  3.   

    双重检查在java编译器上是行不通的,Test类的初始化与instance变量的赋值顺序不可预料。所以第二种办法不能用
      

  4.   

    我发现我第二种方法一行写错了,同步锁那部是用的Test.Class锁
    synchronized (Test.class) {
      

  5.   

      类加载时只会在堆区和方法区分配内存,并把static变量赋值为变量默认值,比如Object变量为null,int变量为0;而当jvm虚拟机初始化类时(加载类不等于初始化类),才真正把变量初始化,即赋值instance 为new Test()。在以下几种情况会对类进行初始化:1第一次为类创建对象,2第一次调用类的静态方法或者静态属性。在我上面那个例子里,静态变量和构造方法都是私有的,只有getInstance方法可以被调用,所以只有当外部第一次调用Test.getInstance时,才会真正将instance赋值为new Test()的引用。所以我想即使是第一种单例的方法,也应该是真正赋值时才会New Test对象,应该也是懒汉模式吧?所以我觉得以上两种方法的分歧不在于延时不延时new Test对象,而是在于第一种方法如果类初始化不是线程安全的,可能会new 多个Test对象,多占一点内存,但这些对象只有最后一个被赋值给Instance的才被引用,其他对象都没被引用,可以被gc释放。而第二种方法每次getInstance多了一部检测,但double check保证了第一次初始化时是线程安全的,而且getInstance方法本身并没有加线程锁,可以并发访问。
      

  6.   

    double check参见http://www.ibm.com/developerworks/java/library/j-dcl.html,
    保证了初始化instance时是线程安全的,而且getInstance方法是可并发的。
      

  7.   

    由于 JAVA 内存模型的限制,第二种方法在 JAVA 中行不通!要使用第二种方法的话,必须在 JDK 5 或以上的版本,并且在 private static Test instance = null; 加上 volatile 关键字。因此,建议使用第一种方法。
      

  8.   

    参考:The "Double-Checked Locking is Broken" Declaration
    http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.htmlDouble-checked locking and the Singleton pattern
    http://www.ibm.com/developerworks/java/library/j-dcl.html
      

  9.   

        你给的第一个链接我大约看了一下了,我很迷茫的是1public Helper getHelper() 居然不是static方法可能是他笔误;2synchronized(this),他加的是this锁 ,即对象锁,而不是class锁,比较我写的,synchronized (Test.class) ,加的是class的锁,我怀疑是他加的对象锁所以不能运行。可能double check一定要在1.5以上运行,但不需要volatile 关键字,因为我目前的项目就是jdk1.5,单例用的double check模式,而并没有加volatile 关键字。
      

  10.   

    java has no double check,《多处理器编程的艺术》P42
      

  11.   


    你不加 volatile 的话,那很有可能在并发的情况下产生不止一个实例。因为第一个线程在实例化的时候,第二个线程进来时看到的可能是之前的东西(这是 JAVA 内存模型决定的)可以看一下这篇文章:volatile 变量使用指南
    http://www.ibm.com/developerworks/cn/java/j-jtp06197.html
      

  12.   

        我彻底看了一遍相关材料,就double check来说确实要加volatile 修饰Instance才能真正保证正确的单例,否则可能有线程拿到没初始化完全的Test对象。这点很感谢火龙果的指导。
        我现在还有个问题,就是用第一种单例方法,private static final Test instance = new Test();
    在类初始化时,会赋值instance为Test对象,那么类初始化是线程安全的么?我只知道类加载ClassLoader.loadClass没有synchronized 关键字,不清楚类初始化是否是线程安全。如果类初始化不是线程安全的,那么第一种方法依旧可能产生多个事例。
      

  13.   

    java.lang.ClassLoader#loadClass
    java.net.URLClassLoader#loadClass
    sun.misc.Launcher$AppClassLoader#loadClass这些方法都有 synchronized 啊?
      

  14.   

       jdk1.6,
       java.lang.ClassLoader#loadClass方法定义:
       public Class<?> loadClass(String name) throws ClassNotFoundException {
    return loadClass(name, false);
        }
        protected synchronized Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
        {....
        }
          
        我之前只看过loadClass(String name)没有synchronized,没去看实现是靠有synchronized的loadClass(name, false),所有的系统ClassLoader加载类都是线程安全的,惭愧...
        又看了点资料,类的初始化,是由jvm统一负责并且保证保证线程安全,所以第一种方法绝对没问题,而double check费力又没效果,下周一重构代码,再次感谢火龙果