3

次のJavaコードを使用します。

public class SomeClass {
  private boolean initialized = false;
  private final List<String> someList; 

  public SomeClass() {
    someList = new ConcurrentLinkedQueue<String>();
  }

  public void doSomeProcessing() {
    // do some stuff...
    // check if the list has been initialized
    if (!initialized) {
      synchronized(this) {
        if (!initialized) {
          // invoke a webservice that takes a lot of time
          final List<String> wsResult = invokeWebService();
          someList.addAll(wsResult);
          initialized = true;
        }
      } 
    }
    // list is initialized        
    for (final String s : someList) {
      // do more stuff...
    }
  }
}

秘訣は、doSomeProcessing特定の条件下でのみ呼び出されることです。リストの初期化は非常に費用のかかる手順であり、まったく必要ない場合があります。

ダブルチェックのイディオムが壊れている理由についての記事を読んだことがあり、このコードを見たときは少し懐疑的でした。ただし、この例の制御変数はブール値であるため、私が知る限り、単純な書き込み命令が必要です。

someListまた、がとして宣言されfinal、同時リストへの参照を保持しwrites いることに注意してくださいreadsConcurrentLinkedQueueリストの代わりに単純なArrayListまたはLinkedList、として宣言されている場合でも、発生する必要はありませfinal-の前に。writesreads

それで、上記のコードはデータの競合がないのですか?

4

5 に答える 5

5

では、Java 言語仕様を取得しましょう。セクション 17.4.5では、事前発生を次のように定義しています。

2 つのアクションは、先行発生関係によって順序付けできます。あるアクションが別のアクションの前に発生する場合、最初のアクションは 2 番目のアクションよりも前に表示され、順序付けされます。x と y の 2 つのアクションがある場合、hb(x, y) と書き、x が y の前に発生することを示します。

  • x と y が同じスレッドのアクションであり、プログラムの順序で x が y の前にある場合、hb(x, y) になります。
  • オブジェクトのコンストラクターの最後から、そのオブジェクトのファイナライザー (§12.6) の開始までの先行発生エッジがあります。
  • アクション x が後続のアクション y と同期する場合、hb(x, y) も得られます。
  • hb(x, y) と hb(y, z) の場合、hb(x, z) です。

2 つのアクション間に事前発生関係が存在するからといって、必ずしも実装でその順序で実行する必要があるとは限らないことに注意してください。並べ替えが合法的な実行と一致する結果をもたらす場合、それは違法ではありません。

次に、2 つのディスカッションに進みます。

より具体的には、2 つのアクションが事前発生関係を共有している場合、それらが事前発生関係を共有していないコードに対して、必ずしもその順序で発生したように見える必要はありません。たとえば、別のスレッドでの読み取りとデータ競合している 1 つのスレッドでの書き込みは、それらの読み取りに対して順不同で発生するように見える場合があります。

あなたのインスタンスでは、スレッドチェック

if (!initialized)

initializedに追加されたすべての書き込みを確認する前に新しい値を確認することができるためsomeList、部分的に入力されたリストを操作できます。

あなたの主張に注意してください

someListまた、が宣言されfinal、並行リストへの参照を保持しいることに注意してください。writes reads

無関係です。はい、スレッドがリストから値を読み取った場合、その値の書き込みの前に発生したこともすべて確認していると結論付けることができます。しかし、値を読み取れない場合はどうなるでしょうか。リストが空のように見える場合はどうなりますか? また、値を読み取ったとしても、後続の書き込みが実行されたことを意味するわけではないため、リストが不完全に見える場合があります。

于 2011-02-19T19:10:34.493 に答える
4

ウィキペディアvolatileは、キーワードを使用することを提案しています。

于 2011-02-19T17:05:23.353 に答える
3

この場合、を使用ConcurrentLinkedQueueしてもデータ競合がないことは保証されません。そのjavadocは言う:

他の並行コレクションと同様に、オブジェクトを ConcurrentLinkedQueue に配置する前のスレッド内のアクションは、別のスレッド内の ConcurrentLinkedQueue からのその要素へのアクセスまたは削除に続くアクションの前に発生します。

つまり、次の場合に一貫性が保証されます。

// Thread 1
x = 42;
someList.add(someObject);

// Thread 2
if (someList.peek() == someObject) {
    System.out.println(x); // Guaranteed to be 42
}

したがって、この場合、 でx = 42;並べ替えることはできませんsomeList.add(...)。ただし、この保証は逆の状況には適用されません。

// Thread 1
someList.addAll(wsResult);
initialized = true;

// Thread 2
if (!initialized) { ... }
for (final String s : someList) { ... }

この場合initialized = true;でも、 で並べ替えることができますsomeList.addAll(wsResult);

したがって、ここでは追加の保証のない通常のダブルチェックイディオムがあるためvolatile、Bozho が示唆するように、 を使用する必要があります。

于 2011-02-19T17:17:10.587 に答える
0

まず、並行キューの間違った使い方です。複数のスレッドがキューに入れたり、キューからポーリングしたりする状況を対象としています。必要なのは、一度初期化され、その後読み取り専用のままになるものです。単純なリスト impl で十分です。

volatile ArrayList<String> list = null;

public void doSomeProcessing() {
    // double checked locking on list
    ...

頭の体操だけを目的として、並行キューを使用してスレッド セーフを実現したいとします。

static final String END_MARK = "some string that can never be a valid result";

final ConcurrentLinkedQueue<String> queue = new ...

public void doSomeProcessing() 
    if(!queue.contains(END_MARK)) // expensive to check!
         synchronized(this)
            if(!queue.contains(END_MARK))
                  result = ...
                  queue.addAll(result);
                  // happens-before contains(END_MARK)==true
                  queue.add( END_MARK );

     //when we are here, contains(END_MARK)==true

     for(String s : queue)
         // remember to ignore the last one, the END_MARK

変数を宣言するとき、一部のインターフェイスではなく、完全なクラス型を使用したことに注意してください。「任意の List impl に変更でき、変更する場所は 1 つしかない」Listため、interface を宣言する必要があると誰かが主張した場合、その人はあまりにも素朴です。

于 2011-02-19T21:02:30.603 に答える
0

初期化済みフラグの代わりに、someList.isEmpty() を確認できますか?

于 2011-02-19T17:42:55.093 に答える