3

データベース呼び出しの結果への参照を設定するために AtomicReference.compareAndSet を使用することは適切ですか? のような質問への回答を探しています。しかし、異なる要件があります。

目標は、ObjectWithSideEffectConstructor副作用の重複を避けるために、インスタンスを 1 回だけ作成することです。構築は で行う必要がありsetUp()ます。複数のスレッドが を呼び出しますsetUp()。同様にtearDown()、オブジェクトからリソースを回収するための がありますが、ここでは省略されています。質問: 目標を達成するためのベスト プラクティスは何ですか?

AtomicReference副作用として、コンストラクターが最初に実行されるため、単に使用するだけでは十分ではありません。

private static AtomicReference<ObjectWithSideEffectConstructor> ref =
  new AtomicReference<ObjectWithSideEffectConstructor>()

void setUp() {
  ref.compareAndSet(null, new ObjectWithSideEffectConstructor());
}

データベース呼び出しの結果への参照を設定するために AtomicReference.compareAndSet を使用することは適切ですか?からの回答を使用します。volatile同期が取れていないため、機能しません。複数のスレッドが入るウィンドウがありますif

private static volatile ObjectWithSideEffectConstructor obj;

void setUp() {
  if (obj == null) obj = new ObjectWithSideEffectConstructor();
}

簡単な修正は

private static ObjectWithSideEffectConstructor obj;
private static final Object monitor = new Object();

void setUp() {
  synchronized (monitor) {
    if (obj == null) obj = new ObjectWithSideEffectConstructor();
  }
}

同様に、揮発性モニターを使用する DCL を使用すると、読み取りパフォーマンスが向上する場合があります。ただし、どちらもある程度の同期が必要なため、パフォーマンスが低下することが予想されます。

も使えますFutureTask。オブジェクトが作成されると、後続FutureTask.get()はブロックせずに戻るため、より効率的です。しかし、それは間違いなく よりもはるかに複雑ですsynchronized

private static final AtomicReference<FutureTask<ObjectWithSideEffectConstructor>> ref =
  new AtomicReference<FutureTask<ObjectWithSideEffectConstructor>>();

void setUp() {
  final FutureTask<ObjectWithSideEffectConstructor> future =
    new FutureTask<ObjectWithSideEffectConstructor>(
      new Callable<ObjectWithSideEffectConstructor>() {
        @Override
        public ObjectWithSideEffectConstructor call() throws InterruptedException {
          return new ObjectWithSideEffectConstructor();
        }
      }
    );
  if (ref.compareAndSet(null, future)) future.run();
  ref.get().get();
}

提案をありがとう。

4

6 に答える 6

1

シングルトンには常に enum 型を使用してください。これにより、シングルトンがエレガントに強制されるだけでなく、シングルトンがそのスーパークラスから clone() メソッドを継承し、プログラマーがプライベート メソッド宣言でオーバーライドするのを忘れた場合などの一般的なプログラミング エラーも防止されます。または、デシリアライズ可能をオーバーライドするのを忘れて、プログラマーがシングルトンをシリアライズし、新しいインスタンスを宣言してから、古いインスタンスをデシリアライズできるようにした場合。

または、静的ファクトリ パターンを使用する場合は、インスタンス フィールドを一時的に宣言し、readresolve メソッドを使用できます。これにより、後の設計プロセスでシングルトンにするかどうかについて気が変わった場合に柔軟に対応できます。

クレジット: J Bloch による効果的な Java に基づく回答 (項目 3)、すべての Java プログラマーが定期的に読み、所有し、参照する必要がある本です...

于 2013-10-15T10:58:02.643 に答える
0

同期された方法が進むべき道です。実際にパフォーマンスが必要な場合は、コードを再構築してシングルスレッドの事前初期化を行う必要があります。他の形式を使用すると、シングルトン パターンで説明されている副作用が発生します。

于 2013-10-12T00:28:59.007 に答える
0

ObjectWithSideEffectConstructorが1 つだけ必要であると仮定します。ここで、1) 回避したい副作用が 2 回発生するのか、それとも 2) 一貫した (シングルトンの) 参照で終わる必要があるのか​​、という質問があります。

いずれにせよsynchronized、良い標準オプションです。最初のスレッドがセットアップされている間、他のスレッドが2 番目のインスタンスを構築するのを防ぎます。

状況 1) にある場合は、おそらく同期を使用する必要があります。起動後のパフォーマンスが重要な場合は、同期セクションのAtomicReference.get()に高速パスを配置して、起動完了後に同期セクションを回避できるようにすることを検討できます。

状況 2) にある場合、-質問からは明確ではありません-構築の副作用がありますが、それを複製することは気にしません-クライアントコードのみ」一貫した単一の参照を参照します。

その 2 番目のケースでは、既に初期化されているかどうかを確認し、そうであれば返すことができます。AtomicReference.get()その後、スレッドは「レース セクション」に入り、そこで (場合によっては複数の) ObjectWithSideEffectConstructor を構築します。最後に、compareAndSet1 つのスレッドのみがシングルトンを設定するようにします。失敗したスレッドAtomicReference.get()は正しいシングルトンを取得するためにフォールバックします。

パフォーマンスに関しては、 への 1 回の呼び出しAtomicReferenceはブロックよりも高速synchronizedですが、二重および三重のチェックと不要な副作用オブジェクトの構築により、2 番目のアプローチになるかどうかはわかりません。synchronized繰り返しますが、単純なブロックはより単純で高速です。

いくつかの測定値を見てみたいと思います。

于 2013-10-12T00:13:58.113 に答える