In realtà mi sono preso il tempo di studiare la fonte reale, per pura curiosità, e l'idea alla base è abbastanza semplice. La versione più recente al momento della stesura di questo post è la 3.2.1.
Esiste un buffer che memorizza eventi pre-allocati che conterrà i dati affinché i consumatori possano leggerli.
Il buffer è supportato da un array di flag (array intero) della sua lunghezza che descrive la disponibilità degli slot del buffer (vedere più avanti per i dettagli). L'array è accessibile come un java # AtomicIntegerArray, quindi ai fini di questa spiegazione si può anche supporre che sia uno.
Possono esserci un numero qualsiasi di produttori. Quando il produttore vuole scrivere nel buffer, viene generato un numero lungo (come nel chiamare AtomicLong # getAndIncrement, il Disruptor utilizza effettivamente la propria implementazione, ma funziona allo stesso modo). Chiamiamo questo generato a lungo un producerCallId. Allo stesso modo, un consumerCallId viene generato quando un consumatore ENDS legge uno slot da un buffer. Si accede al consumerCallId più recente.
(Se ci sono molti consumatori, viene scelta la chiamata con l'ID più basso.)
Questi ID vengono quindi confrontati e se la differenza tra i due è minore rispetto al lato buffer, il produttore può scrivere.
(Se producerCallId è maggiore della recente consumerCallId + bufferSize, significa che il buffer è pieno e il produttore è costretto ad aspettare il bus fino a quando non diventa disponibile uno spot.)
Al produttore viene quindi assegnato lo slot nel buffer in base al suo callId (che è prducerCallId modulo bufferSize, ma poiché bufferSize è sempre una potenza di 2 (limite applicato alla creazione del buffer), l'operazione attuall utilizzata è producerCallId & (bufferSize - 1 )). È quindi libero di modificare l'evento in quello slot.
(L'algoritmo effettivo è un po 'più complicato, e comporta la memorizzazione nella cache di un consumerId recente in un riferimento atomico separato, a fini di ottimizzazione.)
Quando l'evento è stato modificato, la modifica è "pubblicata". Quando si pubblica il rispettivo slot nell'array flag viene riempito con il flag aggiornato. Il valore flag è il numero del loop (producerCallId diviso per bufferSize (di nuovo poiché bufferSize ha una potenza di 2, l'operazione effettiva è uno spostamento a destra).
Allo stesso modo possono esserci un numero qualsiasi di consumatori. Ogni volta che un consumatore desidera accedere al buffer, viene generato un consumerCallId (a seconda di come i consumatori sono stati aggiunti al disgregatore, l'atomico utilizzato nella generazione dell'id può essere condiviso o separato per ciascuno di essi). Questo consumerCallId viene quindi confrontato con il più recente producCCallId e, se è minore dei due, il lettore può avanzare.
(Allo stesso modo se producerCallId è pari a consumerCallId, significa che il buffer è vuoto e il consumatore è costretto ad aspettare. Il modo di attesa è definito da WaitStrategy durante la creazione del disgregatore.)
Per i singoli consumatori (quelli con il proprio generatore di identità), la cosa successiva controllata è la capacità di consumare in batch. Gli slot nel buffer vengono esaminati in ordine da quello relativo al consumerCallId (l'indice è determinato nello stesso modo dei produttori), a quello relativo al recente producerCallId.
Vengono esaminati in un ciclo confrontando il valore del flag scritto nell'array del flag, con un valore del flag generato per consumerCallId. Se le bandiere corrispondono, significa che i produttori che riempiono gli slot hanno commesso le loro modifiche. In caso contrario, il loop viene interrotto e viene restituito il changeId con il commit più alto. Gli slot da ConsumerCallId a ricevuti in changeId possono essere consumati in batch.
Se un gruppo di consumatori legge insieme (quelli con generatore di ID condiviso), ognuno accetta solo un singolo ID di chiamata e viene controllato e restituito solo lo slot per quel singolo ID di chiamata.