对于初始化可能失败的懒惰单例的哪个实现?
想象一下,你有一个静态的无参数方法,它是幂等的,并且总是返回相同的值,并且可能抛出一个已检查的异常,如下所示:
class Foo {
public static Pi bar() throws Baz { getPi(); } // gets Pi, may throw
}
现在,如果构造返回的Object的东西很昂贵并且永远不会改变,那么这对于懒惰的单例是一个很好的选择。一种选择是Holder模式:
class Foo {
static class PiHolder {
static final Pi PI_SINGLETON = getPi();
}
public static Pi bar() { return PiHolder.PI_SINGLETON; }
}
不幸的是,这不起作用,因为我们不能从(隐式)静态初始化程序块中抛出一个已检查的异常,所以我们可以尝试这样的事情(假设我们想保留调用者获取检查异常时的行为)叫bar()
):
class Foo {
static class PiHolder {
static final Pi PI_SINGLETON;
static {
try {
PI_SINGLETON = = getPi(); }
} catch (Baz b) {
throw new ExceptionInInitializerError(b);
}
}
public static Pi bar() throws Bar {
try {
return PiHolder.PI_SINGLETON;
} catch (ExceptionInInitializerError e) {
if (e.getCause() instanceof Bar)
throw (Bar)e.getCause();
throw e;
}
}
在这一点上,双重检查锁定可能更干净?
class Foo {
static volatile Pi PI_INSTANCE;
public static Pi bar() throws Bar {
Pi p = PI_INSTANCE;
if (p == null) {
synchronized (this) {
if ((p = PI_INSTANCE) == null)
return PI_INSTANCE = getPi();
}
}
return p;
}
}
DCL仍然是反模式吗?还有其他解决方案我在这里缺少(像racy单一检查这样的小变种也是可能的,但是不要从根本上改变解决方案)?是否有充分的理由选择其中一个?
我没有尝试上面的例子,所以完全有可能他们不编译。
编辑:我没有重新实现或重新构建这个单例的消费者(即ѭ5的调用者)的奢侈,也没有机会引入DI框架来解决这个问题。我最感兴趣的是在给定约束内解决问题的答案(提供带有检查异常传播给调用者的单例)。
更新:毕竟我决定选择DCL,因为它提供了保存现有合同的最简洁方法,没有人提供为什么应该避免正确完成DCL的具体原因。我没有在接受的答案中使用该方法,因为它似乎只是一种过于复杂的方式来实现同样的事情。
没有找到相关结果
已邀请:
4 个回复
场竟矩喘崩
辅奈
泻伴墓荒
......或者如果懒惰很重要:
(细节将在某种程度上取决于您的DI框架) 您可以告诉DI框架将对象绑定为单例。从长远来看,这为您提供了更大的灵活性,使您的课程更具单元可测性。 另外,我的理解是Java中的双重检查锁定不是线程安全的,因为JIT编译器可能会重新排序指令。编辑:正如meriton所指出的,双重检查锁定可以在Java中工作,但您必须使用volatile关键字。 最后一点:如果您使用的是好的模式,通常很少或没有理由希望您的类被懒惰地实例化。最好让你的构造函数非常轻量级,并将大部分逻辑作为方法的一部分来执行。我并不是说你在这个特殊情况下做错了什么,但是你可能想要更广泛地看看你如何使用这个单例,看看是不是有更好的方法来构建东西。
谷起
您如何期望字段访问引发异常? 编辑:正如Irreputable指出的那样,如果访问导致类初始化,并且初始化因静态初始化程序抛出异常而失败,那么实际上在这里得到了ExceptionInInitializerError。但是,VM在第一次失败后不会尝试再次初始化类,并使用不同的异常进行通信,如下面的代码所示:
结果是:
而不是ExceptionInInitializerError。 您的双重检查锁定会遇到类似的问题;如果构造失败,则该字段保持为空,并且每次访问PI时都会尝试构造“12”。如果失败是永久性和昂贵的,您可能希望以不同的方式做事。