【问题标题】:What is the advantage of Bill pugh solution over Lazy Initialized Singleton solution?Bill pugh 解决方案与 Lazy Initialized Singleton 解决方案相比有什么优势?
【发布时间】:2018-09-18 07:25:59
【问题描述】:

比尔·普格解决方案

public class ThreadSafeSerializedSafeSingleton implements Serializable {

    private ThreadSafeSerializedSafeSingleton() {
    }

    private static class SingletonHelper {
        private static final ThreadSafeSerializedSafeSingleton instance = new ThreadSafeSerializedSafeSingleton();
    }

    public static ThreadSafeSerializedSafeSingleton getInstance() {
        return SingletonHelper.instance;
    }

}

VS

延迟初始化的线程安全单例

public class ThreadSafeSingleton {

    private static ThreadSafeSingleton instance;

    private ThreadSafeSingleton() {
    }

    public static synchronized ThreadSafeSingleton getInstance() {
        if (instance == null) {
            instance = new ThreadSafeSingleton();
        }
        return instance;
    }

}

【问题讨论】:

标签: java singleton


【解决方案1】:

Bill Pugh 的解决方案相对于所呈现的惰性初始化单例实现的优势是与性能相关的。考虑下一个场景。

实例已经初始化并且两个线程同时请求实例

  • 对于惰性初始化实现,由于方法是同步的,因此其中一个线程会阻塞。
  • 对于 Bill Pugh 实施,不会有任何阻塞。

无论如何,这可以(部分)通过双重检查锁定实现单例来缓解。请参见下面的示例。

双重检查锁定单例:

public final class DoubleCheckedLockingSingleton {

    private static volatile DoubleCheckedLockingSingleton instance;

    private DoubleCheckedLockingSingleton(){
        if(instance!=null)
            throw new RuntimeException();
    }

    public static final DoubleCheckedLockingSingleton getInstance(){
        if(instance==null){
            synchronized(DoubleCheckedLockingSingleton.class) {
                if(instance==null)
                    instance = new DoubleCheckedLockingSingleton();
            }
        }
        return instance;
    }

}

在这种情况下,性能差异是微不足道的。双重检查锁定实现和持有者模式实现之间的主要区别在于它起作用的原因。

  • 双重检查锁定:线程只有在同时触发实例创建时才会阻塞。您必须重新检查同步块内的instance == null,因为两个线程可能(虽然极不可能 - 最好的错误类型)在第一个 if 和同步块之间交换。您还必须声明变量 volatile 以利用 JMM '发生在' volatile 保证(因此您可以确定 instance==null 检查在实例完全初始化之前不会返回 false) .
  • 持有者模式:线程只有在同时触发实例创建时才会阻塞。您只需要知道 Java 中的类是延迟加载的,并带有来自 JVM 的锁定(因此锁定和可见性都是开箱即用的,您无需付出额外的努力)。

就个人而言,我更喜欢持有人模式而不是双重检查锁定,因为它的工作原理似乎更容易理解(至少对我而言)。

最后一点,如果您的要求允许(即,如果您使用 DI 框架,例如 Spring),实现单例的最佳方法是让 Spring 为您提供单例(通过使用 @987654324 @ 具有默认的单例范围)。

【讨论】:

  • 好吧!在那种情况下,Bill pugh 解决方案比双重检查锁定解决方案有什么优势?
  • 使用双重检查锁定(通过忘记再次检查同步块中的实例 == null 或忘记声明实例易失性)编写一个实现不佳的单例要容易得多用持有人成语写得很糟糕。 Holder 成语让程序员更难搞砸。除此之外,我不知道有什么优势:)。
【解决方案2】:

这实际上被称为“持有人”模式。

其优点概述here

静态辅助字段的初始化被推迟到调用 getInstance() 方法。必要的happens-before关系是由类加载器的加载和初始化Holder实例的动作与Java内存模型(JMM)提供的保证相结合而创建的。对于延迟初始化静态字段 [Bloch 2008],这个习惯用法比双重检查锁定习惯用法更好。但是,这个习语不能用于延迟初始化实例字段 [Bloch 2001]。

这里的“真正”答案:“双重检查锁定”问题有很多解决方案,它们的优缺点都略有不同。

【讨论】:

    【解决方案3】:

    明显的优点是Bill Pugh方法中没有同步。

    我通常在enum 方法中使用第三个选项,但这只是个人喜好。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-05-20
      • 1970-01-01
      • 2018-04-02
      • 2021-06-23
      • 2022-12-16
      • 2015-09-12
      • 2014-11-14
      • 1970-01-01
      相关资源
      最近更新 更多