比尔·普格解决方案
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;
}
}
答案 0 :(得分:1)
这实际上称为“持有人”模式。
here概述了它的优点:
静态帮助程序字段的初始化推迟到调用getInstance()方法之前。通过类加载器的加载和初始化Holder实例的动作以及Java内存模型(JMM)提供的保证的组合,可以创建必要的先于关系。与懒惰地初始化静态字段相比,此惯用语是比双重检查锁定惯用语更好的选择[Bloch 2008]。但是,该惯用法不能用于延迟初始化实例字段[Bloch 2001]。
这里的“真实”答案是:“双重检查锁定”问题有很多解决方案,而且它们都有各自的优缺点。
答案 1 :(得分:1)
比尔·普格(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。 >)。 就个人而言,我更喜欢持有人模式而不是双重检查锁定,因为它的工作原理似乎更容易理解(至少对我而言是如此)。
最后一点,如果您的要求允许(例如,如果您使用的是DI框架,例如Spring),则实现单例的最佳方法是让Spring为您提供单例(通过使用{{ 1}}(具有默认的单例作用域)。
答案 2 :(得分:0)
显而易见的优点是,Bill Pugh
方法中没有同步。
我通常在enum
方法中使用第三个选项,但这只是个人喜好。