【问题标题】:Singleton instance instantiation单例实例化
【发布时间】:2013-06-13 17:32:14
【问题描述】:

我开始习惯 Java 中的关键字staticvolatile。关于我正在构建的单例类,为什么我会看到以下设计?

public class Singleton{
    private static volatile Singleton _instance; 

    public static Singleton getInstance(){

       if(_instance == null){
           synchronized(Singleton.class){
               if(_instance == null)
                   _instance = new Singleton();
           }

       }
       return _instance;

    }
}

而不是这个?

public class Singleton{ 
    private static volatile Singleton _instance = new Singleton();

    public static Singleton getInstance(){
        return _instance;
    }
}

第一个设计比第二个设计有什么优势吗?我能看到的唯一优点是第二种设计同步了整个方法,这可能需要阻塞整个方法。但我对此的反面想法是,该方法只有一行,根本不会明显阻塞,对吧?

【问题讨论】:

  • “明显阻塞”是什么意思?仅仅因为我们无法注意到汇编语言的指令时间并不意味着它们在现实生活中并不重要。
  • 我很确定第一个示例不需要对 null _instance 进行两次检查。因此可以删除第二次检查。在此类中,实例无法从 new'd 变为 null。
  • @user619818 第二次检查是必要的,因为自上次检查以来另一个线程可能已经初始化了实例。
  • 啊,如果你得到线程之间的上下文切换。好的。
  • @DaveNewton 当我说“明显阻塞”时,我的意思是从每个设计中调用每个 getInstance() 方法所需的时间没有区别。

标签: java static synchronization singleton volatile


【解决方案1】:

我本来希望添加评论,但似乎我还不能这样做。

第一个例子是双重检查锁定,在 Java 中不再被破坏:

从 J2SE 5.0 开始,此问题已得到修复。 volatile 关键字现在可确保多个线程正确处理单例实例。 ~http://en.wikipedia.org/wiki/Double-checked_locking

双重检查锁定的思想是在需要时才创建单例对象,并尽可能少地使用同步锁定对象。

第二个示例将在加载类后立即创建单例(可能在启动时),无论它是否曾经使用过。

这两个示例都是线程安全的,并且从 J2SE 5.0 开始完全可用。这仅取决于您是否愿意承担创建单例对象的成本,并且在内存中保存一个永远不会被使用的对象是可以接受的。

【讨论】:

    【解决方案2】:

    实际上第二个执行“惰性”初始化,所以如果你的类的实例化可能需要很多时间并且你不确定你的应用程序中是否需要这个类的实例。在这种情况下,您可以“按需”创建一个实例。 根据多线程环境 - 两种设计都是线程安全的。

    【讨论】:

      【解决方案3】:

      第一个示例对多线程应用程序很有用,但证明不安全,除非您使用 volatile 关键字。它被称为双重检查锁定单例模式

      这个想法是: - 检查对象是否为null,如果不是返回它并避免锁定。 - 如果是null,锁定lock类。 - 再次检查它是否是null,因为其他线程可能在当前线程之前锁定了它。 - 如果它仍然为空,则实例化它。

      使用 volatile 解决了这个问题,因为它确保其他线程在前一个线程完成之前不会读取该变量,但锁定和同步在 CPU 时间方面仍然是昂贵的操作。

      正确的做法是使用比尔·普格的解决方案

      public class Singleton {
          // Private constructor prevents instantiation from other classes
          private Singleton() { }
      
          /**
          * SingletonHolder is loaded on the first execution of Singleton.getInstance() 
          * or the first access to SingletonHolder.INSTANCE, not before.
          */
          private static class SingletonHolder { 
                  public static final Singleton INSTANCE = new Singleton();
          }
      
          public static Singleton getInstance() {
                  return SingletonHolder.INSTANCE;
          }
      }
      

      它是线程安全的,并且是正确的方法。这也是一种惰性初始化,因为只有在引用内部类时才加载内部类,并且 static 关键字确保每个类只有一个实例。

      您的第二个示例有效,但没有延迟初始化。

      【讨论】:

      • 为什么不需要将 INSTANCE 变量指定为 volatile?有没有可能每个线程都有自己的实例缓存并且可能变得陈旧?
      • volatile 确保其他线程在前一个线程完成之前不会读取它。
      • 那么如果不将INSTANCE设置为volatile,是不是两个线程可以同时修改实例并相互覆盖呢?
      • 这篇文章是2001年的,在1.5+以下不再有效,是否是最好的方法是一个单独的问题。
      • @Darijan 好吧,不。 1. 宣布最终结果可确保不存在可见性问题。 2. 即使你没有声明它是最终的,但实际上是不可变的(在这种情况下就是这样),因为它被内联初始化为静态字段,它也不会有可见性问题
      猜你喜欢
      • 2011-06-10
      • 2023-04-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-09
      • 2011-01-13
      • 2021-04-04
      相关资源
      最近更新 更多