这个问题实际上是指一个different question,因为它可能没有很好地制定而被关闭了。
对于此代码示例(在多线程环境中),什么是有效的替代延迟初始化习惯而不是双重检查锁定:
public class LazyEvaluator {
private final Object state;
private volatile LazyValue lazyValue;
public LazyEvaluator(Object state) {
this.state = state;
}
public LazyValue getLazyValue() {
if (lazyValue == null) {
synchronized (this) {
if (lazyValue == null) {
lazyValue = new LazyValue(someSlowOperationWith(state), 42);
}
}
}
return lazyValue;
}
public static class LazyValue {
private String name;
private int value;
private LazyValue(String name, int value) {
this.name = name;
this.value = value;
}
private String getName() {
return name;
}
private int getValue() {
return value;
}
}
}
编辑更新以包含缓慢操作并添加有关多线程环境的明确提及
答案 0 :(得分:3)
如果我了解你,那么你可以改变这个
public LazyValue getLazyValue() {
if (lazyValue == null) {
synchronized (this) {
if (lazyValue == null) {
lazyValue = new LazyValue(state.toString());
}
}
}
return lazyValue;
}
到这个
public synchronized LazyValue getLazyValue() {
if (lazyValue == null) {
lazyValue = new LazyValue(state.toString());
}
return lazyValue;
}
但它只需要pre-Java 5(它不支持volatile的获取/发布语义),如果多个线程可能访问LazyEvaluator
的同一个实例。如果每个线程都有一个线程本地实例,那么您不需要同步。
答案 1 :(得分:3)
最简单的解决方案是
public LazyValue getLazyValue() {
return new LazyValue(state.toString(), 42);
}
因为LazyValue
是琐碎的对象,根本不值得记住。
如果涉及昂贵的计算,您可以通过声明其字段LazyValue
将final
转换为真正的不可变对象:
public static class LazyValue {
private final String name;
private final int value;
// …
这样你甚至可以通过数据竞赛发布实例:
// with lazyValue even not being volatile
public LazyValue getLazyValue() {
return lazyValue!=null? lazyValue:
(lazyValue=new LazyValue(state.toString(), 42));
}
在这种情况下,值可能会多次计算,因为多个线程同时访问它,但一旦线程看到非null
值,它就会正确由于final
字段初始化保证而导致的初始化值。
如果计算如此昂贵,即使不太可能的并发计算也必须避免,那么只需声明getLazyValue()
synchronized
,因为与计算相比,其开销可以忽略不计将被保存。
最后,如果你真的遇到一个场景,那么计算是如此繁重,必须不惜一切代价避免重叠的并发计算,但是分析显示后来的同步是一个瓶颈,你可能遇到过一个非常罕见的情况被双重检查锁定可能是一种选择(非常罕见)。
在这种情况下,您的问题代码仍然可以替代。将DCL与我上面的建议相结合,即将所有LazyValue
的字段声明为final
,并将lazyValue
持有人字段设为非volatile
。这样,您甚至可以在构造惰性值之后保存volatile
读取。但是,我仍然说,它应该真的很少需要。
也许这就是为什么DCL有如此多的负面声誉的非技术原因:它在讨论中(或在StackOverflow上)的出现与其实际需求完全不相称。
答案 2 :(得分:1)
好吧,“有效的替代懒惰初始化习惯用法”留下了很大的灵活性,所以我会把我的两分钱放在环中,注意这可能是应用库的好地方。特别是番石榴。 https://code.google.com/p/guava-libraries/
// You have some long method call you want to make lazy
MyValue someLongMethod(int input) { ... }
// So you wrap it in a supplier so it's lazy
Supplier<MyValue> lazy = new Supplier<MyValue>() {
public MyValue get() {
return someLongMethod(2);
}
}
// and you want it only to be called once ...
Supplier<MyValue> cached = Suppliers.memoize(lazy);
// ... and someLongMethod won't actually be called until
cached.get();
Suppliers
类使用(正确)双重检查锁定。就成语而言,供应商肯定是有效且非常受欢迎的--java.util.function.Supplier来自Java 8。