【发布时间】:2011-03-02 20:53:48
【问题描述】:
假设你有一个静态的无参数方法,它是幂等的并且总是返回相同的值,并且可能会抛出一个检查异常,如下所示:
class Foo {
public static Pi bar() throws Baz { getPi(); } // gets Pi, may throw
}
如果构造返回的对象的东西很昂贵并且永远不会改变,那么现在这是一个懒惰的单例的好选择。一种选择是 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 single check 之类的小变体,但不要从根本上改变解决方案)?是否有充分的理由选择其中一个?
我没有尝试上面的示例,所以它们完全有可能无法编译。
编辑:我没有重新实现或重新构建这个单例的消费者(即Foo.bar() 的调用者)的奢侈,我也没有机会引入一个DI框架来解决这个问题。我最感兴趣的是在给定约束内解决问题的答案(提供带有已检查异常的单例传播给调用者)。
更新:毕竟我决定使用 DCL,因为它提供了最干净的方式来保存现有合同,并且没有人提供应该避免正确执行 DCL 的具体原因。我没有在接受的答案中使用该方法,因为它似乎只是实现同一目标的一种过于复杂的方法。
【问题讨论】:
标签: java design-patterns concurrency singleton double-checked-locking