Na verdade, dediquei um tempo para estudar a fonte real, por pura curiosidade, e a idéia por trás disso é bastante simples. A versão mais recente no momento da redação deste post é 3.2.1.
Há um buffer que armazena eventos pré-alocados que retêm os dados para os consumidores lerem.
O buffer é apoiado por uma matriz de sinalizadores (matriz inteira) de seu comprimento que descreve a disponibilidade dos slots do buffer (veja mais detalhes). O array é acessado como um java # AtomicIntegerArray, portanto, para o propósito desta explicação, você também pode supor que seja um.
Pode haver qualquer número de produtores. Quando o produtor deseja gravar no buffer, um número longo é gerado (como na chamada AtomicLong # getAndIncrement, o Disruptor realmente usa sua própria implementação, mas funciona da mesma maneira). Vamos chamar isso gerado por muito tempo de producerCallId. De maneira semelhante, um consumerCallId é gerado quando um consumidor TERMINA lendo um slot de um buffer. O consumerCallId mais recente é acessado.
(Se houver muitos consumidores, a chamada com o ID mais baixo será escolhida.)
Esses IDs são comparados e, se a diferença entre os dois for menor que o lado do buffer, o produtor poderá escrever.
(Se o producerCallId for maior que o recente consumerCallId + bufferSize, isso significa que o buffer está cheio e o produtor é forçado a esperar no barramento até que um local fique disponível.)
O produtor recebe o slot no buffer com base em seu callId (que é o módulo bufferSize do prducerCallId, mas, como o bufferSize é sempre uma potência de 2 (limite imposto na criação do buffer), a operação real usada é o producerCallId & (bufferSize - 1 )). É grátis modificar o evento nesse slot.
(O algoritmo real é um pouco mais complicado, envolvendo o cache de consumerId recente em uma referência atômica separada, para fins de otimização.)
Quando o evento foi modificado, a alteração é "publicada". Ao publicar o respectivo slot na matriz de sinalizadores, é preenchido com o sinalizador atualizado. O valor do sinalizador é o número do loop (producerCallId dividido por bufferSize (novamente porque bufferSize é a potência de 2, a operação real é uma mudança à direita).
De maneira semelhante, pode haver qualquer número de consumidores. Sempre que um consumidor deseja acessar o buffer, é gerado um consumerCallId (dependendo de como os consumidores foram adicionados ao disruptor, o atômico usado na geração de ID pode ser compartilhado ou separado para cada um deles). Este consumerCallId é então comparado ao producentCallId mais recente e, se for menor dos dois, o leitor poderá progredir.
(Da mesma forma, se o producerCallId for igual ao consumerCallId, isso significa que o buffer é oitenta e o consumidor é forçado a esperar. A maneira de esperar é definida por um WaitStrategy durante a criação do disruptor.)
Para consumidores individuais (aqueles com seu próprio gerador de identificação), a próxima coisa verificada é a capacidade de consumir em lote. Os slots no buffer são examinados em ordem do respectivo ao consumerCallId (o índice é determinado da mesma maneira que para os produtores), ao do respectivo ao recente producerCallId.
Eles são examinados em um loop comparando o valor do sinalizador gravado na matriz do sinalizador com um valor do sinalizador gerado para o consumerCallId. Se as bandeiras coincidirem, significa que os produtores que preencheram os espaços confirmaram suas alterações. Caso contrário, o loop será interrompido e o mais alto changeId confirmado será retornado. Os slots de ConsumerCallId para recebidos em changeId podem ser consumidos em lote.
Se um grupo de consumidores lê em conjunto (aqueles com gerador de identificação compartilhada), cada um recebe apenas um único callId e apenas o slot desse único callId é verificado e retornado.