Entiendo que, en general, los flujos de Java no se dividen. Sin embargo, tenemos una tubería complicada y larga, al final de los cuales tenemos dos tipos diferentes de procesamiento que la participación de la primera parte de la tubería.
Debido al tamaño de los datos, almacenar el producto corriente intermedia no es una solución viable. Tampoco se está ejecutando la tubería dos veces.
Básicamente, lo que estamos buscando es una solución que es una operación en una corriente que los rendimientos de dos (o más) corrientes que se llenaban con pereza y capaces de ser consumidos en paralelo. Por eso, quiero decir que si la corriente A se divide en corrientes B y C, cuando los flujos de B y C consumen 10 elementos, consume corriente A y presta esos 10 elementos, pero si la corriente B a continuación, trata de consumir más elementos, bloquea hasta que la corriente C también los consume.
¿Hay alguna solución pre-hechos para este problema o cualquier biblioteca podemos ver? Si no es así, ¿dónde estaríamos empezar a mirar si queremos poner en práctica esto por nosotros mismos? ¿O hay una razón de peso para no aplicado en absoluto?
Puede implementar una costumbre Spliterator
con el fin de lograr este tipo de comportamiento. Vamos a dividir las corrientes en el común "fuente" y los diferentes "consumidores". El spliterator personalizado, entonces envía los elementos de la fuente a cada consumidor. Para este fin, vamos a utilizar una BlockingQueue
(véase esta pregunta ).
Tenga en cuenta que la parte difícil aquí no es el spliterator / corriente, pero la sincronización de los consumidores de todo la cola, como los comentarios sobre su pregunta ya indican. Aún así, sin embargo se implementa la sincronización, Spliterator
le ayuda a utilizar corrientes con él.
@SafeVarargs
public static <T> long streamForked(Stream<T> source, Consumer<Stream<T>>... consumers)
{
return StreamSupport.stream(new ForkingSpliterator<>(source, consumers), false).count();
}
private static class ForkingSpliterator<T>
extends AbstractSpliterator<T>
{
private Spliterator<T> sourceSpliterator;
private BlockingQueue<T> queue = new LinkedBlockingQueue<>();
private AtomicInteger nextToTake = new AtomicInteger(0);
private AtomicInteger processed = new AtomicInteger(0);
private boolean sourceDone;
private int consumerCount;
@SafeVarargs
private ForkingSpliterator(Stream<T> source, Consumer<Stream<T>>... consumers)
{
super(Long.MAX_VALUE, 0);
sourceSpliterator = source.spliterator();
consumerCount = consumers.length;
for (int i = 0; i < consumers.length; i++)
{
int index = i;
Consumer<Stream<T>> consumer = consumers[i];
new Thread(new Runnable()
{
@Override
public void run()
{
consumer.accept(StreamSupport.stream(new ForkedConsumer(index), false));
}
}).start();
}
}
@Override
public boolean tryAdvance(Consumer<? super T> action)
{
sourceDone = !sourceSpliterator.tryAdvance(queue::offer);
return !sourceDone;
}
private class ForkedConsumer
extends AbstractSpliterator<T>
{
private int index;
private ForkedConsumer(int index)
{
super(Long.MAX_VALUE, 0);
this.index = index;
}
@Override
public boolean tryAdvance(Consumer<? super T> action)
{
// take next element when it's our turn
while (!nextToTake.compareAndSet(index, index + 1))
{
}
T element;
while ((element = queue.peek()) == null)
{
if (sourceDone)
{
// element is null, and there won't be no more, so "terminate" this sub stream
return false;
}
}
// push to consumer pipeline
action.accept(element);
if (consumerCount == processed.incrementAndGet())
{
// start next round
queue.poll();
processed.set(0);
nextToTake.set(0);
}
return true;
}
}
}
Con el enfoque utilizado, los consumidores trabajan en cada elemento en paralelo, sino que esperan el uno al otro antes de comenzar en el siguiente elemento.
Problema conocido Si uno de los consumidores es "más corto" que los otros (por ejemplo, porque llama limit()
) sino que también detener a los otros consumidores y dejar los hilos colgando.
Ejemplo
public static void sleep(long millis)
{
try { Thread.sleep((long) (Math.random() * 30 + millis)); } catch (InterruptedException e) { }
}
streamForked(Stream.of("1", "2", "3", "4", "5"),
source -> source.map(word -> { sleep(50); return "fast " + word; }).forEach(System.out::println),
source -> source.map(word -> { sleep(300); return "slow " + word; }).forEach(System.out::println),
source -> source.map(word -> { sleep(50); return "2fast " + word; }).forEach(System.out::println));
fast 1
2fast 1
slow 1
fast 2
2fast 2
slow 2
2fast 3
fast 3
slow 3
fast 4
2fast 4
slow 4
2fast 5
fast 5
slow 5