两种单例实现同样多线程证明?

Both Singleton implementations equally Multi Threading proof?

下面的代码片段中有两个单例实现。 它们都是在 Java 中的多线程环境中实现单例的正确方法吗?

如果是这样,是不是说明第二种实现方式就完全没用了,想实现单例,就用enum方式吧?

我写单例的首选方式:

public enum Singleton {
  INSTANCE();
}

另一种写单例的方式(显式同步):

public class Singleton {

  private static Singleton instance = null;
  private Singleton() {}

  public static synchronized Singleton getInstance() {
    if (instanz == null) {
      instanz = new Singleton();
    }
    return instanz;
  }

两者都是线程安全的,但第一个使用枚举可避免不必要的锁定,在大多数情况下更可取。

你们这两种方法看起来都不错。但是,对于创建 Singleton,我建议使用以下方法,它是 100% 线程安全的并且没有同步问题,因为 它利用了 Java 的 class 加载机制。

P.S.: 感谢 @james 触发。

懒惰的单例:
像这样 class Provider 只会被加载一次,因此只有一个 Network class 实例将存在,并且没有两个线程可以在同一个 JVM 中创建 2 个实例,因为 class 不会被加载两次.

优点:

  • 仅在需要时创建实例。
  • 调用getInstance()时无需承担同步开销,当有getInstance()同步时才会发生
  • class中还可以有其他静态方法可以不创建实例调用,只在需要调用实例方法时才创建实例。这是惰性单例的一个很好的优势。

    public class Network {
    private Network(){
    
    }
    
    private static class Provider {
        static final Network INSTANCE = new Network();
    }
    
    public static Network getInstance() {
        return Provider.INSTANCE;
    }
    //More code...
    

    }

渴望单例:

private static Network INSTANCE = null;

private Network(){
}

static {
    INSTANCE = new Network();
}

public static Network getInstance() {
    return INSTANCE;
}


最后:你说得对,你指定的第二种方法确实是最后的选择,因为它必须承担每次 2 个线程尝试时的同步成本获取单例实例。

现在,它的 ENUM 单例游戏 v/s 懒惰的单例。我已经为您提供了几个链接,说明为什么人们不喜欢 ENUM 作为单例,以及 ENUM 单例的可扩展性 v/s 基于 class 加载的惰性单例。

这两种方法都可以无缝工作,所以它的选择问题,因此正确的答案是 "It depends!!!" :)

第一个解决方案适用于创建单例,因为您想要对某事物的单个实例建模。在此解决方案中,如果在创建实例期间发生异常,例如未找到文件,您将获得 classnotfound 异常。

第二种解决方案适用于创建单例时,因为对象的创建需要很长时间,并且涉及到对文件系统或网络的访问。

一些程序员之所以讨论如何创建单例的方式,是因为他们希望支持惰性创建,但担心访问器方法的后续调用的性能。

因此您的 class 使用 synchronized 实现了惰性创建方面,但后续调用可能会受到当时不必要的 synchronized 开销的影响。为了克服这个假设的开销,发明了臭名昭著的双重检查锁定,这引起了更多关于这个实际上完全不相关的“问题”的讨论:

如果你想将惰性创建与单例结合起来,你可以使用:

public class Singleton {
    static final Singleton INSTANCE=new Singleton();
    private Singleton(){}
    public static Singleton getInstance() {
        return INSTANCE;
    }
}

整个构造是惰性的,因为 class 初始化不会在第一个线程调用 getInstance() 之前发生并且它是线程安全的,因为 class 初始化本身保证JVM 线程安全。这是最有效的解决方案,因为后续调用将只读取 final 字段而无需同步。

这正是使用

时会发生的情况
public enum Singleton {
  INSTANCE;
}

它使您无需声明 private 构造函数,因为编译器会为您做这些。并且该字段隐式为 static final。一个区别是字段本身是公开的,不需要访问器方法。

此外,enums 本质上支持序列化。存储常量时,不会存储实例数据,只会存储类型和常量名称。在反序列化时,将查找当前运行时实例。所以如果你需要序列化支持,enum 应该是首选,否则没有太大区别(使用 enum 可以节省一些输入)。

但是

  • 您的应用程序多久需要一次单例? (如果你说“经常”,你应该重新考虑你的软件设计)
  • 与您的应用程序相关的单例访问器方法的性能多久一次?
  • 单例实例创建的惰性有多频繁?
  • 如果您需要单例,那么您真正需要关于单例的“铁一般”保证的频率是多少?属性?即,如果有人设法使用肮脏的反射技巧创建第二个实例,那么可能出现的问题是的问题,不是吗?

简单地说,Singleton Design Pattern 似乎被严重高估了,关于如何有效实现它的讨论甚至更多……