De hecho, me tomé el tiempo para estudiar la fuente real, por pura curiosidad, y la idea detrás de esto es bastante simple. La versión más reciente al momento de escribir esta publicación es 3.2.1.
Hay un búfer que almacena eventos preasignados que contendrán los datos para que los consumidores los lean.
El búfer está respaldado por una matriz de banderas (matriz de enteros) de su longitud que describe la disponibilidad de las ranuras del búfer (para más detalles, ver más detalles). Se accede a la matriz como un Java # AtomicIntegerArray, por lo que para el propósito de esta explicación, también puede suponer que es una.
Puede haber cualquier cantidad de productores. Cuando el productor quiere escribir en el búfer, se genera un número largo (como al llamar a AtomicLong # getAndIncrement, el Disruptor en realidad usa su propia implementación, pero funciona de la misma manera). Llamemos a esto generado por mucho tiempo un Id. De manera similar, se genera un ConsumerCallId cuando un consumidor termina leyendo una ranura de un búfer. Se accede al último ConsumerCallId.
(Si hay muchos consumidores, se elige la llamada con el ID más bajo).
Estos identificadores se comparan, y si la diferencia entre los dos es menor que el lado del búfer, el productor puede escribir.
(Si el productorCallId es mayor que el reciente consumerCallId + bufferSize, significa que el búfer está lleno y el productor se ve obligado a esperar en el bus hasta que haya un lugar disponible).
Luego, al productor se le asigna la ranura en el búfer en función de su callId (que es prducerCallId modulo bufferSize, pero dado que el bufferSize siempre es una potencia de 2 (límite impuesto en la creación del búfer), la operación real utilizada es productorCallId & (bufferSize - 1 )). Entonces es libre de modificar el evento en ese espacio.
(El algoritmo real es un poco más complicado, ya que involucra el almacenamiento en caché del ID de consumidor reciente en una referencia atómica separada, para fines de optimización).
Cuando se modificó el evento, el cambio se "publica". Al publicar el espacio respectivo en la matriz de banderas se llena con la bandera actualizada. El valor del indicador es el número del bucle (productorCallId dividido por bufferSize (de nuevo, ya que bufferSize tiene una potencia de 2, la operación real es un desplazamiento a la derecha).
De manera similar, puede haber cualquier número de consumidores. Cada vez que un consumidor quiere acceder al búfer, se genera un consumerCallId (dependiendo de cómo se agregaron los consumidores al disruptor, el atómico utilizado en la generación de id se puede compartir o separar para cada uno de ellos). Este ConsumerCallId se compara con el más reciente producentCallId, y si es menor de los dos, el lector puede progresar.
(Del mismo modo, si el productorCallId es incluso para el consumidorCallId, significa que el búfer está vacío y el consumidor se ve obligado a esperar. La forma de espera se define mediante una Estrategia de espera durante la creación del disruptor).
Para los consumidores individuales (los que tienen su propio generador de identificación), lo siguiente que se verifica es la capacidad de consumo por lotes. Las ranuras en el búfer se examinan en orden desde el respectivo al consumerCallId (el índice se determina de la misma manera que para los productores), hasta el respectivo al RecentCallId reciente.
Se examinan en un bucle comparando el valor del indicador escrito en la matriz de indicadores, con un valor de indicador generado para el consumerCallId. Si las banderas coinciden, significa que los productores que ocupan los espacios han comprometido sus cambios. Si no, el bucle se rompe y se devuelve el changeId comprometido más alto. Las ranuras de ConsumerCallId para recibir en changeId se pueden consumir en lote.
Si un grupo de consumidores lee juntos (los que tienen un generador de identificación compartida), cada uno solo toma un único callId, y solo se comprueba y devuelve la ranura para ese único callId.