2

通常我使用第一个实现。几天前,我发现了另一个。谁能解释一下这两种实现之间的区别?第二个实现是线程安全的?在第二个例子中使用内部类有什么好处?

//--1st Impl
public class Singleton{

      private static Singleton _INSTANCE;

      private Singleton() {}
      public static Singleton getInstance(){
          if(_INSTANCE == null){
               synchronized(Singleton.class){
                      if(_INSTANCE == null){
                          _INSTANCE = new Singleton();
                      }
               }
          }
      return _INSTANCE;
      }  
}

//--2nd Impl
public class Singleton {
      private Singleton() {}

      private static class SingletonHolder { 
            private static final Singleton _INSTANCE = new Singleton();
      }

      public static Singleton getInstance() {
            return SingletonHolder._INSTANCE;
      }
}
4

4 回答 4

7

第一个实现使用所谓的“双重检查锁”。这是一件非常糟糕的事情。它看起来是线程安全的,但实际上并非如此。

第二种实现确实是线程安全的。

关于为什么第一个实现被破坏的解释相当复杂,所以我建议您获取一份 Brian Goetz 的Java Concurrency in Practice以获取详细解释。简短的版本是允许编译器_INSTANCE在构造函数完成之前分配变量,这可能导致第二个线程看到部分构造的对象。

于 2012-12-01T04:27:27.483 回答
3

第一个实现只有且只有当_INSTANCE变为易失性时才是线程安全的。第二个是线程安全的,因为只有在类加载器加载_INSTANCE后才会初始化。SingletonHolder

因此,当访问内部类时(比加载整个程序要晚得多),类加载器会加载内部类并初始化变量。因此,对于以后的任何访问,该对象都是现成的,因此该方法getInstance()是线程安全的。

第二种实现的美妙之处在于,您不必担心同步或计入类加载器为您做的事情

于 2012-12-01T04:28:20.960 回答
1

#1 旨在确保延迟初始化。但是,在给定的情况下,#2 也确保了延迟初始化。_INSTANCE 仅在加载 Singleton.class 时创建,并且 Singleton.class 在首次调用 getSingleton() 时加载。类中没有其他方法。不需要双重检查锁定。当然在#1 _INSTANCE 应该是易变的。

注意:我不同意双重检查锁定是不好的。如果正确实施,它可能非常有用。

于 2012-12-01T05:10:55.970 回答
0

第一个代码片段是双重检查锁定习惯用法的示例,它曾经非常流行,但现在已知不安全,永远不应该使用。

第二个片段使用finalJava 语言规范定义的类加载语义和关键字语义的组合,以确保延迟初始化和线程安全,因此要好得多。

于 2012-12-01T04:30:37.677 回答