0

私はLazyReferenceクラスを数年間使用しています(もちろん定期的にではありませんが、非常に役立つ場合もあります)。クラスはここで見ることができます。クレジットは、Robbie Vanbrabant (クラス作成者) と Joshua Bloch の著名な「Effective Java 2nd edt」に贈られます。(元のコード)。

クラスは (Java 5+ で) 正しく動作しますが、潜在的な問題が 1 つあります。instanceProvider返された場合(Guiceの契約nullに従ってはいけませんが…)、メソッドを実行するたびに LOCK が保持され、何度も呼び出されます。契約を破った人には良い罰のように見えますが (he-he)、値を設定する可能性のあるフィールドを遅延して初期化する必要がある場合はどうすればよいでしょうか?Provider.get()LazyReference.get()instanceProvider.getnull

LazyReference を少し変更しました。

public class LazyReference<T> {

  private final Object LOCK = new Object();

  private volatile T instance;

  private volatile boolean isNull;

  private final Provider<T> instanceProvider;

  private LazyReference(Provider<T> instanceProvider) {
    this.instanceProvider = instanceProvider;
  }

  public T get() {
    T result = instance;
    if (result == null && !isNull) {
      synchronized (LOCK) {
        result = instance;
        if (result == null && !isNull) {
          instance = result = instanceProvider.get();
          isNull = (result == null);
        }
      }
    }
    return result;
  }
}

私見は問題なく動作するはずです(別の意見がある場合は、コメントや批判を投稿してください)。しかし、ブール値volatileから修飾子を削除するとどうなるでしょうか(もちろんそのままにしておきます)。それでも正しく動作しますか?isNullinstance

4

2 に答える 2

3

上記のコードには競合状態があります。isNull が設定される前に、instanceProvider.get() の結果からインスタンスが「実際の」null に設定される可能性があります。

この複雑なナンセンスを捨てて、適切に同期するのは簡単ではないでしょうか。パフォーマンスの違いを測定することはできず、コードが正しいことを確認する方が簡単になるでしょう。

于 2011-06-22T19:01:38.887 に答える
2

Neil Coffey が指摘したように、このコードには競合状態が含まれていますが、次のように簡単に修正できます (instanceそうである必要はないことに注意してくださいvolatile)。

public class LazyReference<T> {     
  private T instance;
  private volatile boolean initialized;
  ...
  public T get() {
    if (!initialized) {
      synchronized (LOCK) {
        if (!initialized) {
          instance = instanceProvider.get();
          initialized = true;
        }
      }
    }
    return instance;
  }
}
于 2011-06-22T19:29:47.280 に答える