純粋な好奇心から、私は実際に時間をかけて実際の情報源を研究しましたが、その背後にある考えは非常に単純です。この投稿の執筆時点での最新バージョンは3.2.1です。
コンシューマーが読み取るデータを保持する、事前に割り当てられたイベントを格納するバッファーがあります。
バッファーは、バッファースロットの可用性を説明するその長さのフラグの配列(整数配列)によってサポートされます(詳細については、詳細を参照してください)。配列はjava#AtomicIntegerArrayのようにアクセスされるため、この説明の目的のために、配列が1であると想定することもできます。
プロデューサーはいくつでも存在できます。プロデューサーがバッファーに書き込みたい場合、長い数が生成されます(AtomicLong#getAndIncrementを呼び出す場合と同様に、Disruptorは実際には独自の実装を使用しますが、同じように動作します)。この生成されたlongをプロデューサーコールIDと呼びましょう。同様に、コンシューマーがバッファーからスロットの読み取りを終了すると、consumerCallIdが生成されます。最新のconsumerCallIdにアクセスします。
(コンシューマーが多数ある場合は、最小のIDを持つ呼び出しが選択されます。)
次にこれらのIDが比較され、2つのIDの差がバッファー側よりも小さい場合、プロデューサーは書き込みを許可されます。
(producerCallIdが最近のconsumerCallId + bufferSizeより大きい場合、それはバッファーがいっぱいであることを意味し、スポットが利用可能になるまでプロデューサーはバス待機を強制されます。)
次に、プロデューサーには、callId(prducerCallId modulo bufferSize)に基づいてバッファー内のスロットが割り当てられますが、bufferSizeは常に2の累乗(バッファーの作成時に適用される制限)であるため、使用される実際の操作はproducerCallId&(bufferSize-1 ))。その後、そのスロットのイベントを自由に変更できます。
(実際のアルゴリズムはもう少し複雑で、最適化のために、最近のconsumerIdを別のアトミック参照にキャッシュします。)
イベントが変更された場合、変更は「公開」されます。フラグ配列のそれぞれのスロットをパブリッシュすると、更新されたフラグが入ります。フラグ値は、ループの数です(producerCallIdをbufferSizeで割ったものです(ここでもbufferSizeは2の累乗なので、実際の操作は右シフトです)。
同様に、任意の数の消費者が存在する可能性があります。コンシューマーがバッファーにアクセスするたびに、consumerCallIdが生成されます(コンシューマーがディスラプターにどのように追加されたかに応じて、ID生成で使用されるアトミックを共有または個別に共有できます)。次に、このconsumerCallIdが最新のproducentCallIdと比較され、2つのうちの方が少ない場合、リーダーは進行を許可されます。
(同様に、producerCallIdがconsumerCallIdである場合、それはバッファーが空であり、コンシューマーが強制的に待機することを意味します。待機の方法は、ディスラプターの作成中にWaitStrategyによって定義されます。)
個々のコンシューマー(独自のIDジェネレーターを持つもの)の場合、次にチェックされるのは、バッチ消費する機能です。バッファー内のスロットは、consumerCallId(インデックスはプロデューサーの場合と同じ方法で決定されます)に対応するものから、最近のproducerCallIdに対応するものに向かって順に調べられます。
これらは、フラグ配列に書き込まれたフラグ値を、consumerCallIdに対して生成されたフラグ値と比較することにより、ループで検査されます。フラグが一致する場合、スロットを埋めているプロデューサーが変更をコミットしたことを意味します。そうでない場合、ループは中断され、コミットされた最高の変更IDが返されます。ConsumerCallIdからchangeIdで受信されるまでのスロットは、バッチで消費できます。
コンシューマーのグループが一緒に読み取る場合(共有IDジェネレーターを持つグループ)、それぞれが単一のcallIdのみを受け取り、その単一のcallIdのスロットのみがチェックされて返されます。