7

映画館を模したマルチスレッド アプリケーションを作成しています。関与する各人は独自のスレッドであり、同時実行はセマフォによって完全に行われる必要があります。私が抱えている唯一の問題は、基本的にスレッドをリンクして通信できるようにする方法です(たとえば、パイプを介して)。

例えば:

スレッドである Customer[1] は、Box Office まで歩けるようにするセマフォを取得します。ここで、Customer[1] は Box Office Agent に、映画「X」を見たいと伝えなければなりません。次に、BoxOfficeAgent[1] もスレッドで、映画が満杯でないことを確認し、チケットを販売するか、Customer[1] に別の映画を選択するように指示する必要があります。

セマフォとの同時実行性を維持しながら、そのデータをやり取りするにはどうすればよいですか?

また、java.util.concurrent から使用できる唯一のクラスはSemaphoreクラスです。

4

2 に答える 2

8

スレッド間でデータをやり取りする簡単な方法の 1 つはBlockingQueue<E>、パッケージにあるインターフェイスの実装を使用することjava.util.concurrentです。

このインターフェイスには、さまざまな動作で要素をコレクションに追加するメソッドがあります。

  • add(E): 可能な場合は追加し、そうでない場合は例外をスローします
  • boolean offer(E): 要素が追加されている場合は true、そうでない場合は false を返します
  • boolean offer(E, long, TimeUnit): 要素の追加を試行し、指定された時間待機します
  • put(E): 要素が追加されるまで、呼び出しスレッドをブロックします

また、同様の動作で要素を取得するためのメソッドも定義します。

  • take(): 要素が利用可能になるまでブロックします
  • poll(long, TimeUnit): 要素を取得するか、null を返します

私が最も頻繁に使用する実装はArrayBlockingQueue、 、LinkedBlockingQueueおよびSynchronousQueueです。

最初のArrayBlockingQueueは固定サイズで、コンストラクターに渡されるパラメーターによって定義されます。

2 番目のLinkedBlockingQueueはサイズに制限がありません。常にすべての要素を受け入れます。つまり、offerすぐに true を返しadd、例外をスローすることはありません。

3 つ目、そして私にとって最も興味深いのSynchronousQueueは、まさにパイプです。サイズ 0 のキューと考えることができます。要素を保持することはありません。このキューは、他のスレッドが要素を取得しようとしている場合にのみ要素を受け入れます。逆に、要素をプッシュしようとしている別のスレッドがある場合にのみ、取得操作は要素を返します。

セマフォのみで同期を行うという宿題の要件を満たすために、SynchronousQueue について説明した内容に触発されて、非常によく似たものを書くことができます。

class Pipe<E> {
  private E e;

  private final Semaphore read = new Semaphore(0);
  private final Semaphore write = new Semaphore(1);

  public final void put(final E e) {
    write.acquire();
    this.e = e;
    read.release();
  }

  public final E take() {
    read.acquire();
    E e = this.e;
    write.release();
    return e;
  }
}

このクラスは、SynchronousQueue について説明したものと同様の動作を示すことに注意してください。

メソッドput(E)が呼び出されると、同じメソッドへの別の呼び出しが最初の行でブロックされるように、空のままになる書き込みセマフォを取得します。このメソッドは、渡されたオブジェクトへの参照を格納し、読み取りセマフォを解放します。take()このリリースでは、メソッドを呼び出すすべてのスレッドが続行できるようになります。

メソッドの最初のステップはtake()、当然のことながら、他のスレッドが要素を同時に取得できないようにするために、読み取りセマフォを取得することです。要素が取得され、ローカル変数に保持された後 (演習: その行 E e = this.e が削除されたらどうなるでしょうか? )、メソッドは書き込みセマフォを解放しますput(E)。ローカル変数に保存されたものを返します。

重要な注意点として、渡されるオブジェクトへの参照はプライベート フィールドに保持され、メソッドtake()とメソッドput(E)は両方ともfinalであることに注意してください。これは非常に重要であり、しばしば見逃されます。これらのメソッドが final でない場合 (またはさらに悪いことに、フィールドが非公開の場合)、継承クラスは動作を変更してコントラクトtake()put(E)破ることができます。

最後に、次のようtake()に使用すると、メソッドでローカル変数を宣言する必要がなくなります。try {} finally {}

class Pipe<E> {
  // ...
  public final E take() {
    try {
      read.acquire();
      return e;
    } finally {
      write.release();
    }
  }
}

ここで、この例のポイントは、try/finally経験の浅い開発者の間では見過ごされている の使用法を示すことです。明らかに、この場合、実際の利益はありません。

くそー、私はあなたのためにあなたの宿題をほとんど終えました。報復として、そしてセマ​​フォに関する知識をテストするために、BlockingQueue コントラクトで定義された他のメソッドのいくつかを実装してみませんか? たとえば、offer(E)メソッドとtake(E, long, TimeUnit)!

幸運を。

于 2011-04-09T04:40:30.703 に答える