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() {
  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 に答える
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保存される計算と比較してオーバーヘッドが無視できることを宣言するだけです。


最後に、計算が非常に重いシナリオに実際に遭遇した場合、同時実行計算のオーバーラップは何としても回避する必要がありますが、プロファイリングにより後の同期がボトルネックであることが示されている場合、非常にまれなケースの 1 つに遭遇した可能性があります。チェック付きロックがオプションになる可能性があります (非常にまれです)。

この場合、質問のコードに代わるものがまだあります。DCL を上記の私の提案と組み合わせて、すべてLazyValueのフィールドを as として宣言しfinallazyValueホルダー フィールドを non-にしvolatileます。volatileそうすれば、遅延値が構築された後に読み取りを保存することもできます。ただし、私はまだ、それが必要になることはめったにないはずだと言います。

おそらくそれが、DCL が非常に否定的な評判を持っている非技術的な理由です。ディスカッション (または StackOverflow 上) での出現は、その真の必要性とはまったく釣り合いが取れていません。

于 2014-08-13T15:19:01.203 に答える
1

さて、「効果的な代替遅延初期化イディオム」には多くの柔軟性が残されているため、これがライブラリを適用するのに適した場所である可能性があることに注意して、リングに 2 セントを置きます。特にグアバ。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クラスによって (適切に) 使用されます。イディオムに関する限り、Supplier は確かに効果的であり、非常に人気があります --java.util.function.Supplier は Java 8 で導入されました。

幸運を。

于 2014-08-15T09:00:08.963 に答える