この問題に対する簡単な、しかし完全ではない解決策は、実行中の処理スレッドの数と等しい配列内に一連のサブ キューを維持することです。1 つのマスター スレッドが 1 つのメイン キューから項目を取り出し、オブジェクト キーの hashCode (タスクを識別して関連付けるあらゆるものの hashCode) のモジュロを介してインデックス付けされたサブ キューにそれらを追加します。
例えば
int queueIndex = myEntity.getKey().hashCode() % queues.length;
1 つのスレッドのみがそのキューを処理し、同じエンティティのすべてのタスクがそのキューに送信されるため、競合状態は発生しません。
一部のスレッドは他のスレッドよりも大きなキューになる可能性があるため、このソリューションは不完全です。実際には、これが問題になる可能性は低いですが、考慮すべき事項です。
簡単な解決策の問題:
単一のキューからアイテムを取り出してから、影響を受けるエンティティに対して別のものをロックするというより単純なソリューションには、競合状態があります (Aurand が指摘したように)。与えられた:
Master Queue [ Task1(entity1), Task2(entity1), ... ]
task1
とtask2
の両方が同じエンティティを編集し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 のロックを解除します
これを回避するには、各スレッドはキューからタスクを取得する前に何か (キューなど) をロックし、親ロックを保持したままエンティティのロックを取得する必要があります。ただし、この親ロックを保持してエンティティ ロックの取得を待機している間はすべてをブロックしたくないため、エンティティ ロックのみを試行し、取得に失敗した場合にケースを処理する必要があります (おそらく別のキューに入れます)。 . 全体的に状況は自明ではなくなります。