2

私は Java が初めてで、Java の並行性を理解しようとしています。調べているうちに、Java の並行性に関する非常に人気のあるページで次のコードに出くわしました。

public class CrawledSites {
  private List<String> crawledSites = new ArrayList<String>();
  private List<String> linkedSites = new ArrayList<String>();

  public void add(String site) {
    synchronized (this) {
      if (!crawledSites.contains(site)) {
        linkedSites.add(site);
      }
    }
  }


/**
   * Get next site to crawl. Can return null (if nothing to crawl)
   */

  public String next() {
    if (linkedSites.size() == 0) {
      return null;
    }
    synchronized (this) {
      // Need to check again if size has changed
      if (linkedSites.size() > 0) {
        String s = linkedSites.get(0);
        linkedSites.remove(0);
        crawledSites.add(s);
        return s;
      }
      return null;
    }
  }

}

ここでの関数 next() は、以下の行のように相互排除に違反していると思います。

if (linkedSites.size() == 0) {
  return null;
}

同期ブロックの外に保持されるため、一部のスレッドが add() または next() で同期ブロック内のlinkedSitesを変更している場合、他のスレッドはそれを読み取ることができます.

間違っている場合は修正してください。

4

3 に答える 3

2

おっしゃる通りです。コードの作成者は、同期セクションに入る前に、linkedSites 配列が空でないことを確認することで、少し時間を節約するために何か巧妙なことをしていると考えたのではないでしょうか。同期セクション内でサイズが再度チェックされるため、これは安全に見えるかもしれません。

ただし、Java メモリ モデルは、読み取りが同期セクションでも行われない限り、next() を呼び出すスレッドが、最後に変更したスレッドと同じ状態の linkedSites を参照することを保証しません。別のスレッドがそこにデータを入れたにもかかわらず、配列は空です。各スレッドは、同期されたコード ブロックによって他のスレッドのコピーとのみ同期されるオブジェクト データの独自のコピーを持つ可能性があります。そのため、次に呼び出すスレッドは、配列が空であると誤って認識する可能性があります。

于 2015-01-08T12:49:44.867 に答える