Я насправді взяв час, щоб вивчити власне джерело, з великої цікавості, і ідея за ним досить проста. Найновіша версія на момент написання цієї публікації - 3.2.1.
Існує буфер, який зберігає заздалегідь виділені події, які дозволять зберігати дані для споживачів.
Буфер підтримується масивом прапорів (цілочисленний масив) його довжини, який описує наявність буферних слотів (детальніше див. Далі). До масиву звертається як до java # AtomicIntegerArray, тому для цілей цього пояснення ви також можете вважати його єдиним.
Виробників може бути будь-яка кількість. Коли виробник хоче записати в буфер, генерується довге число (як при виклику AtomicLong # getAndIncrement, Disruptor насправді використовує власну реалізацію, але вона працює таким же чином). Давайте назвемо це згенерованим довгим виробникомCallId. Аналогічним чином, ConsumCallId генерується, коли споживач ENDS читає слот з буфера. Доступ до останнього споживачаCallId.
(Якщо споживачів багато, вибирається дзвінок із найнижчим ідентифікатором.)
Ці ідентифікатори потім порівнюються, і якщо різниця між ними менша від буферної сторони, виробник може записувати.
(Якщо generatorCallId більше, ніж останній споживачCallId + bufferSize, це означає, що буфер заповнений, і виробник змушений чекати, поки пляма стане доступною.)
Потім виробник призначає слот в буфері на основі його callId (який є prducerCallId по модулю bufferSize, але оскільки bufferSize завжди є потужністю 2 (обмеження, що застосовується при створенні буфера), використовувана операційна операція виробникCallId & (bufferSize - 1 )). Тоді вільно змінювати подію в цьому слоті.
(Справжній алгоритм є трохи складнішим, включаючи кешування недавнього споживачаId в окремій атомній посилання для цілей оптимізації.)
Коли подія була змінена, зміна "публікується". При публікації відповідного слота в масиві прапорців заповнюється оновлений прапор. Значення прапора - це число циклу (generatorCallId, розділене на bufferSize (знову ж таки, оскільки bufferSize - потужність 2, фактична операція - це правильний зсув).
Аналогічним чином може бути будь-яка кількість споживачів. Кожен раз, коли споживач хоче отримати доступ до буфера, генерується споживчий виклик (залежно від того, як споживачі були додані до руйнівника, атом, що використовується в генерації ідентифікаторів, може бути спільним або відокремленим для кожного з них). Потім цей споживчийCallId порівнюється з найновішим producentCallId, і якщо він менше двох, читачеві дозволяється прогресувати.
(Аналогічно, якщо виробникCallId навіть до споживачаCallId, це означає, що буфер порожній і споживач змушений чекати. Спосіб очікування визначається WaitStrategy під час створення руйнівника.)
Для окремих споживачів (тих, у кого є власний генератор ідентифікаторів), наступне, що перевіряється, - це можливість партії споживання. Слоти в буфері перевіряються в порядку від відповідного споживачаCallId (індекс визначається таким же чином, як і для виробників), до відповідного для недавнього виробникаCallId.
Вони перевіряються в циклі, порівнюючи значення прапора, записане в масиві прапорів, із значенням прапора, сформованим для споживачаCallId. Якщо прапори збігаються, це означає, що виробники, які заповнюють слоти, здійснили свої зміни. Якщо ні, цикл розривається, і повертається найвище зафіксований changeId. Слоти від ConsumerCallId до отриманих у changeId можна споживати пакетно.
Якщо група споживачів читає разом (ті, у кого є спільний генератор ідентифікаторів), кожен приймає лише один callId, і перевіряється та повертається лише слот для цього єдиного callId.