Dividir Java corriente en dos corrientes perezosas sin el funcionamiento del terminal

Par:

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?

Malte Hartwig:

Puede implementar una costumbre Spliteratorcon 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, Spliteratorle 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

Supongo que te gusta

Origin http://43.154.161.224:23101/article/api/json?id=179062&siteId=1
Recomendado
Clasificación