1

这个问题实际上是指一个不同的问题,因为它可能没有很好地表述,所以它被关闭为重复。

对于此代码示例(在多线程环境中),什么是有效的替代惰性初始化习惯用法而不是双重检查锁定:

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;
      }

  }

}

编辑更新以包括一个缓慢的操作并添加了关于多线程环境的明确提及

4

3 回答 3

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 上)与它的实际需求完全不成比例。

于 2014-08-13T15:19:01.203 回答
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;
}

但这只是 Java 5 之前的版本(它不支持 volatile 的获取/释放语义),并且如果多个线程可能访问您的LazyEvaluator. 如果每个线程都有一个线程本地实例,那么您不需要同步。

于 2014-08-13T15:15:53.737 回答
1

Well, "effective alternative lazy initialization idiom" leaves a lot of flexibility, so I'll put my two cents in the ring by noting that this might be a good place to apply a library. In particular, Guava. 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();

Double-checked-locking is is used (properly) by the Suppliers class. AS far as idioms go, Supplier is certainly effective and quite popular --java.util.function.Supplier came in Java 8.

Good luck.

于 2014-08-15T09:00:08.963 回答