6

たとえば、データ オブジェクトがあるとします。

class ValueRef { double value; }

各データ オブジェクトがマスター コレクションに格納される場所:

Collection<ValueRef> masterList = ...;

ジョブのコレクションもあり、各ジョブにはデータ オブジェクトのローカル コレクションがあります (各データ オブジェクトは にも表示されますmasterList)。

class Job implements Runnable { 
     Collection<ValueRef> neededValues = ...; 
     void run() {
         double sum = 0;
         for (ValueRef x: neededValues) sum += x;
         System.out.println(sum);
     } 
}

使用事例:

  1. for (ValueRef x: masterList) { x.value = Math.random(); }

  2. ジョブ キューにいくつかのジョブを入力します。

  3. スレッドプールを起こす

  4. 各ジョブが評価されるまで待ちます

注: ジョブの評価中、すべての値はすべて一定です。ただし、スレッドは過去にジョブを評価した可能性があり、キャッシュされた値を保持しています。

質問: 各スレッドが最新の値を認識できるようにするために必要な同期の最小量はどれくらいですか?

モニター/ロックの観点からの同期は理解していますが、キャッシュ/フラッシュの観点からの同期は理解していません (つまり、同期ブロックの開始/終了時にメモリ モデルによって何が保証されているか)。

私には、新しい値をメイン メモリにコミットするために値を更新するスレッドで 1 回同期し、新しい値が読み取られるようにキャッシュをフラッシュするためにワーカー スレッドごとに 1 回同期する必要があるように感じます。しかし、これを行う最善の方法がわかりません。

私のアプローチ:グローバル モニターを作成します。次に、マスター リストを更新しながらstatic Object guard = new Object(); 同期します。guard最後に、スレッド プールを開始する前に、プール内のスレッドごとに 1 回guard、空のブロックで同期します。

それは本当にそのスレッドによって読み取られた値の完全なフラッシュを引き起こしますか? それとも、同期ブロック内で触れた値だけですか? その場合、空のブロックの代わりに、各値をループで 1 回読み取る必要がありますか?

御時間ありがとうございます。


編集:私の質問は、同期ブロックを終了すると、(その時点以降の)最初の読み取りはすべてメインメモリに移動するのでしょうか? 何を同期したかに関係なく?

4

3 に答える 3

3

スレッドプールのスレッドが過去にいくつかのジョブを評価したことは問題ではありません。

のJavadocはExecutor言う:

メモリ整合性の影響:Runnableオブジェクトをエグゼキュータに送信する前のスレッドでのアクションが発生します-実行が開始される前に、おそらく別のスレッドで。

したがって、標準のスレッドプールの実装を使用し、ジョブを送信する前にデータを変更する限り、メモリの可視性の影響について心配する必要はありません。

于 2012-06-26T20:47:44.493 に答える
2

あなたが計画していることは十分に聞こえます。「スレッドプールをウェイクアップ」する方法によって異なります。

Java メモリ モデルは、synchronizedブロックに入る前にスレッドによって実行されたすべての書き込みが、その後そのロックで同期するスレッドから見えるようにします。

wait()そのため、マスター リストを更新している間、ワーカー スレッドが呼び出し (ブロック内にある必要があります) でブロックされていることが確実な場合、ワーカー スレッドがsynchronized起動して実行可能になると、マスター スレッドによって行われた変更が可視になります。これらのスレッド。

ただし、java.util.concurrentパッケージ内の高レベルの同時実行ユーティリティを適用することをお勧めします。これらは独自のソリューションよりも堅牢であり、より深く掘り下げる前に同時実行性を学ぶのに適した場所です。


明確にするために:ワーカーに実装するタスクがあるかどうかを確認するためにチェックが行われる同期ブロックを使用せずにワーカースレッドを制御することはほとんど不可能です。したがって、コントローラ スレッドによってジョブに加えられた変更は、ワーカー スレッドが起動する前に発生します。メモリバリアとして機能するには、synchronizedブロック、または少なくとも変数が必要です。volatileただし、これらのいずれかを使用してスレッド プールを作成する方法は考えられません。

java.util.concurrencyパッケージを使用する利点の例として、次のことを考慮してください。呼び出しを含むsynchronizedブロックを使用したり、変数を使用してビジー待機ループを使用したりできます。スレッド間のコンテキスト切り替えのオーバーヘッドにより、ビジー待機は実際には特定の条件下でパフォーマンスが向上することがあります。一見しただけで想定される恐ろしい考えは必要ありません。wait()volatile

コンカレンシー ユーティリティ (この場合はおそらくExecutorService) を使用すると、環境、タスクの性質、および特定の時点での他のスレッドのニーズを考慮して、特定のケースに最適な選択を行うことができます。そのレベルの最適化を自分で達成することは、多くの不必要な作業です。

于 2012-06-26T20:52:40.843 に答える
1

コレクションへの参照を公開した後、コレクション内の値を不変Collection<ValueRef>にするか、少なくとも変更しないでください。ValueRefそうすれば、同期について心配する必要はありません。

コレクションの値を変更し、新しいコレクションを作成して新しい値を入れたい場合です。値が設定されたら、コレクション参照の新しいジョブ オブジェクトを渡します。

これを行わない唯一の理由は、コレクションのサイズが大きすぎてメモリにほとんど収まらず、2 つのコピーを持つ余裕がない場合、またはコレクションの交換によってガベージ コレクターの作業が多すぎる場合です (証明スレッド化されたコードに変更可能なデータ構造を使用する前に、これらのいずれかが問題になります)。

于 2012-06-26T21:19:36.210 に答える