Java:惰性初始化单例
创建单例的模式似乎类似于:Java:惰性初始化单例,java,design-patterns,synchronization,singleton,Java,Design Patterns,Synchronization,Singleton,创建单例的模式似乎类似于: public class Singleton { private static final Singleton instance = new Singleton(); private Singleton(){ } public static Singleton getInstance() { return instance; } } 然而,我的问题是,如果单例构造函数做了一些不利于单元测试的事情,例如
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton(){
}
public static Singleton getInstance()
{
return instance;
}
}
然而,我的问题是,如果单例构造函数做了一些不利于单元测试的事情,例如调用外部服务、jndi查找等,那么如何使用这样的类进行单元化呢
我想我可以像这样重构它:
public class Singleton {
private static Singleton instance;
private Singleton(){
}
public synchronized static Singleton getInstance()
{
if(instance == null)
instance = new Singleton();
return instance;
}
//for the unit tests
public static void setInstance(Singleton s)
{
instancce = s;
}
}
现在的问题是,仅仅为了单元可测试性,我已经强制getInstance同步,所以仅仅为了测试方面,它将对实际应用程序产生负面影响。有没有办法解决这个问题,似乎任何其他类型的延迟初始化都不起作用,因为java中双重锁定模式的性质已被破坏。您可以使用创建单例,并根据环境切换实现
或者,避免使用单例模式,而是使用。您可以将枚举用作单例
enum Singleton {
INSTANCE;
}
假设您的单例在单元测试中做了一些不受欢迎的事情,您可以
// in the unit test before using the Singleton, or any other global flag.
System.setProperty("unit.testing", "true");
Singleton.INSTANCE.doSomething();
enum Singleton {
INSTANCE;
{
if(Boolean.getBoolean("unit.testing")) {
// is unit testing.
} else {
// normal operation.
}
}
}
注意:不需要同步块或显式锁。在访问.class之前不会加载实例,在使用成员之前不会初始化实例。如果只使用Singleton.INSTANCE
而不使用Singleton.class
,则用于初始化的值在以后更改时不会出现问题
编辑:如果只使用
Singleton.class
,则可能不会初始化该类。在本例中,在Java8Update112上没有这样做
public class ClassInitMain {
public static void main(String[] args) {
System.out.println("Printing a class reference");
Class clazz = Singleton.class;
System.out.println("clazz = " + clazz);
System.out.println("\nUsing an enum value");
Singleton instance = Singleton.INSTANCE;
}
static enum Singleton {
INSTANCE;
Singleton() {
System.out.println(getClass() + " initialised");
}
}
}
印刷品
Printing a class reference
clazz = class ClassInitMain$Singleton
Using an enum value
class ClassInitMain$Singleton initialised
您可以依赖注入singleton实例,重写单元测试代码中的getInstance(),使用面向方面的编程来拦截方法调用并返回不同的对象,或者使用类似的工具来模拟几乎任何东西,包括静态、最终类、构造函数以及人们通常说的所有东西“不稳定。” 我在遗留系统中采用的一种方法是修改工厂方法(getInstance)检查系统属性是否有我将实例化的替代实现。这被设置为单元测试套件中的替代模拟对象
至于“double checked locking is breake”语句,如果您使用volatile关键字,并且Java>=1.5,则该语句已被破坏(即使使用volatile)使用1.4和更早版本,但如果您知道您的代码将只在最近的JVM上运行,我不会担心。但我也不会使用单例:让DI/IOC容器管理对象的生命周期将解决这两个问题(可测试性和同步访问器瓶颈)更优雅。在执行单元测试的构建阶段延迟初始化如何。然后在编译分发之前将代码更改回内联初始化 您的生产代码是内联初始化的,测试期间除外。顺便说一句,生产代码和测试代码之间的这种差异可能会引入bug,但哪一个呢
(当然,如果这是一个解决方案,我们让一个构建阶段+工具来完成这项工作。我认为这在maven和中得到了促进)。在每种语言中都打破了双重检查锁定,而不仅仅是Java 我倾向于避免使用单例,但如果需要,可以使用holder模式,正如Josh Bloch的《高效Java:
编辑:修复了引用。hmm我喜欢系统属性的想法,它可以标记单元测试。理想情况下,最好不使用单例,但它比我高一点,旧代码库大,没有时间重新考虑:(.您已经发现了Singleton模式的一个关键缺点:它将您锁定在Singleton接口的特定实现中。是的,您可以通过各种方式解决这个问题,最糟糕的是。相反,问问自己,您是否真的需要一个Singleton。为什么您需要惰性初始化,而不是Java已经自动提供的东西s表示每种类型?即使通过引用
.class
,实例也会加载,但不会初始化。@LewBloch可能有JVM发生这种情况,但Oracle JVM 8 update 112不正确。JLS解释说。类型加载到引用.class
文本上,很明显,否则文本不会在内存中o首先引用。这将加载类型,但不会初始化它。您可能混淆了加载和初始化。引用.class
本身就足以加载类型,但根据JLS的说法,至少返回到Java 1.4,不应该触发类型初始化。在Java 1.5之前,Sun JVM有一个bug,其中此类引用实际上触发了类型初始化。他们在Java 5中修复了该bug,从那时起,该功能就按照规范@LewBloch hmmm运行。我承认,我对它进行了重新编写,因为它令人困惑。尽管我提到了实现单例的holder类解决方案,但这几乎总是过于工程化。simp正如@Peter Lawrey所指出的,最可靠且几乎总是正确的方法是使用单个常量定义枚举。
public class Foo
{
static class Holder
{
static final Foo instance = new Foo();
}
public static Foo getInstance()
{
return Holder.instance;
}
private Foo()
{
}
// ...
}