6

オブジェクトのコレクションを操作するタスクのキューがあります (例として、オブジェクトがアドレス帳のエントリであるとしましょう)。

タスクの例としては、「Joe の電話番号を 888-555-1212 に更新する」などがあります。

複数の「ジョーの電話番号を更新...」タスクを同時にキューに入れることができますが、電話番号は異なります。この場合、状態が最後に正しいことを確認するために、更新を適用する必要があります (いいえ、引数のために、タスクにタイムスタンプを付け、アドレス帳のエントリにタイムスタンプを付けて破棄することはできません)。古いタスク)。

Jane の更新を Joe の更新と順不同で適用しても安全です。

キューのマルチスレッド処理をしたいのですが、人でアクセスを同期する必要があります。

この種の便利なライブラリはありますか?それとも、Executor を使用し、Runnable の run() メソッドで「name」に対して独自の同期を行うことに追いやられていますか?

4

3 に答える 3

3

この問題に対する簡単な、しかし完全ではない解決策は、実行中の処理スレッドの数と等しい配列内に一連のサブ キューを維持することです。1 つのマスター スレッドが 1 つのメイン キューから項目を取り出し、オブジェクト キーの hashCode (タスクを識別して関連付けるあらゆるものの hashCode) のモジュロを介してインデックス付けされたサブ キューにそれらを追加します。

例えば

int queueIndex = myEntity.getKey().hashCode() % queues.length;

1 つのスレッドのみがそのキューを処理し、同じエンティティのすべてのタスクがそのキューに送信されるため、競合状態は発生しません。

一部のスレッドは他のスレッドよりも大きなキューになる可能性があるため、このソリューションは不完全です。実際には、これが問題になる可能性は低いですが、考慮すべき事項です。

簡単な解決策の問題:

単一のキューからアイテムを取り出してから、影響を受けるエンティティに対して別のものをロックするというより単純なソリューションには、競合状態があります (Aurand が指摘したように)。与えられた:

Master Queue [ Task1(entity1), Task2(entity1), ... ]

task1task2の両方が同じエンティティを編集しentity1、 と がキューthread1で動作している場合thread2、予想される/望ましいイベントのシーケンスは次のとおりです。

  • スレッド 1 がタスク 1 を実行
  • スレッド 1 はエンティティ 1 をロックします
  • スレッド 1 がエンティティ 1 を編集します
  • スレッド 1 はエンティティ 1 のロックを解除します
  • スレッド 2 がタスク 2 を実行
  • スレッド 2 はエンティティ 1 をロックします
  • スレッド 2 がエンティティ 1 を編集します
  • スレッド 2 はエンティティ 1 のロックを解除します

残念ながら、ロックがスレッドの run メソッドの最初のステートメントであっても、次のシーケンスが発生する可能性があります。

  • スレッド 1 がタスク 1 を実行
  • スレッド 2 がタスク 2 を実行
  • スレッド 2 はエンティティ 1 をロックします
  • スレッド 2 がエンティティ 1 を編集します
  • スレッド 2 はエンティティ 1 のロックを解除します
  • スレッド 1 はエンティティ 1 をロックします
  • スレッド 1 がエンティティ 1 を編集します
  • スレッド 1 はエンティティ 1 のロックを解除します

これを回避するには、各スレッドはキューからタスクを取得する前に何か (キューなど) をロックし、親ロックを保持したままエンティティのロックを取得する必要があります。ただし、この親ロックを保持してエンティティ ロックの取得を待機している間はすべてをブロックしたくないため、エンティティ ロックのみを試行し、取得に失敗した場合にケースを処理する必要があります (おそらく別のキューに入れます)。 . 全体的に状況は自明ではなくなります。

于 2013-08-21T17:07:05.353 に答える
0

このような競合は、各オブジェクトにバージョンを割り当てることによって常に解決されます。更新ごとにバージョンがインクリメントされます。そのため、1 つの更新プログラムが間違った時期に配信された場合、その更新プログラムは却下されたり、遅れたりする可能性があります。いずれにせよ、どの更新が最初でどれが 2 番目かを判断する方法が必要です。この方法は楽観的ロックと呼ばれます。

于 2013-08-25T11:17:10.730 に答える
-1

考えられる解決策の 1 つ

タスクが何らかのクラスによって記述されていると仮定します

class Task {
  Integer taskGroup;
  // other
}

ここで、 taskGroup は、到着順に処理する必要があるタスクを識別する ID です (この例では、各「名前」は独自の taskGroup を定義できます。より一般的には、同じ名前を持つタスクは同じ taskGroup に属します)。

mainTaskQueueをTaskListオブジェクトのaとします。それで

  • を作成しMap<Integer,List<Task>>、言うtaskGroupsQueues
  • taskGroup ごとに、taskGroupsQueues.get(taskGroup)順次動作するスレッドを作成します。
  • メイン スレッドは、taskメイン タスク リストからa を削除し、mainTaskQueueに追加します。taskGroups.get(task.taskGroup)
  • メイン キューからシングル キューへのタスクの移動と、シングル キューからのフェッチは同期する必要があります。

つまり、同じ名前に属するタスクは同じスレッドで実行されます。

メイン スレッドがタスクの分散を実行する場合、メイン スレッドはある種の負荷分散も実行する可能性があることに注意してください。つまり、順序の一貫性のためにタスクが特定のキューに強制されない場合、そのタスクはショート キューに移動する必要があります。ただし、シングルスレッドになる可能性があるという問題に固有のものです。つまり、同じタスクグループに属しているタスクしかない場合(ケース名)。

別の可能な解決策 (テストされていない、単なる提案)

increment1s の投稿と Aurands のコメントで指摘されているように、トレッド内の taskGroup (名前) の同期にはいくつかの問題があります。基本的には、エグゼキュータが同じ名前で同期しようとする 2 つのスレッドを開始した可能性があるため、手遅れです。ただし、エグゼキューター レベルで実行の順序を確認しようとする場合があります。たとえば、次の投稿を参照してください: Java Executors: タスクの優先度を設定するにはどうすればよいですか? (エグゼキュータに渡された PriorityBlockingQueue を参照します)。

于 2013-08-20T19:56:00.590 に答える