限制无限并行流

时间:2016-05-26 20:33:18

标签: java concurrency java-8 java-stream

1)如何使用供应商(find_program())并行创建大小的N值流,同时确保向供应商发出不超过N个调用?我需要这个,因为我有一个供应商需要进行昂贵的supplier操作。

2)显而易见的'回答我的问题,supplier.get(),不起作用,并且经常导致向供应商发出超过N个调用。这是为什么?

作为'证据' Streams.generate(supplier).limit(N)导致超过N次调用Streams.generate(supplier).limit(N)的事实,请考虑以下代码:

supplier.get()

public class MWE { static final int N_ELEMENTS=100000; static Supplier<IntSupplier> mySupplier = () -> new IntSupplier() { AtomicInteger ai = new AtomicInteger(-1); @Override public int getAsInt() { return ai.incrementAndGet(); } }; public static void main(String[] args) { int[] a = IntStream.generate(mySupplier.get()).limit(N_ELEMENTS).toArray(); int[] b = IntStream.generate(mySupplier.get()).parallel().limit(N_ELEMENTS).toArray(); } } 按预期等于a,但与您预期的相反[0, 1, ..., N_ELEMENTS-1]不包含与b相同的元素。相反,a通常包含大于或等于b的元素,这表示对供应商的调用次数超过N_ELEMENTS

另一个例子是N_ELEMENTS并不总是生成相同的数字集。

2 个答案:

答案 0 :(得分:4)

流API不保证IntStream.generate()将指定次数调用生成器。此电话也不尊重订购。

如果您确实需要增加数字的并行流,那么使用IntStream.range(0, N_ELEMENTS).parallel()会好得多。这不仅可以确保您实际拥有从0N_ELEMENTS-1的所有数字,还可以大大减少争用并保证顺序。如果您需要生成更复杂的内容,请考虑使用定义您自己的Spliterator类的自定义源。

请注意,建议的IntStream.iterate解决方案可能不会大规模并行化,因为它是按顺序排列的。

答案 1 :(得分:1)

不保证调用.limit()会导致供应商生成前N个元素的流,因为Stream.generate()会创建一个unordered流,limit()可以自由发送决定什么&#39; part&#39;要保持的流。实际上,参考&#34;前N个元素&#34;甚至在语义上都不合理。或&#34;(#)的第一部分&#34;,因为流是无序的。这种行为在API文档中清楚地列出;非常感谢所有向我指出这一点的人!

自从提出这个问题以来,我已经为自己的问题提出了两个解决方案。我要感谢Tagir,他让我朝着正确的方向前进。

解决方案1:滥用IntStream.range()

一种简单而有效的方法来创建一个供应商支持的无序,大小的并行流,该供应商不再向供应商发出绝对必要的调用,而是(误)使用IntStream.range()这样:

IntStream.range(0,N_ELEMENTS).parallel().mapToObj($ -> generator.get())

基本上,我们仅使用IntStream.range()来创建可以并行处理的大小的流。

解决方案2:自定义分裂器

因为我们从未实际使用IntStream.range()创建的流内部的整数,所以我们可以通过创建自定义Spliterator来做得更好:

final class SizedSuppliedSpliterator<T> implements Spliterator<T> {
    private int remaining;

    private final Supplier<T> supplier;

    private SizedSuppliedSpliterator(Supplier<T> supplier, int remaining) {
        this.remaining = remaining;
        this.supplier = supplier;
    }

    static <T> SizedSuppliedSpliterator of(Supplier<T> supplier, int limit) {
        return new SizedSuppliedSpliterator(supplier, limit);
    }

    @Override
    public boolean tryAdvance(final Consumer<? super T> consumer) {
        Objects.requireNonNull(consumer);
        if (remaining > 0) {
            remaining--;
            final T supplied = supplier.get();
            consumer.accept(supplied);
            return true;
        }
        return false;
    }

    @Override
    public void forEachRemaining(final Consumer<? super T> consumer) {
        while (remaining > 0) {
            consumer.accept(supplier.get());
            remaining--;
        }
    }

    @Override
    public SizedSuppliedSpliterator<T> trySplit() {
        int split = (int)remaining/2;
        remaining -= split;
        return new SizedSuppliedSpliterator<>(supplier, split);
    }

    @Override
    public long estimateSize() {
        return remaining;
    }

    @Override
    public int characteristics() {
        return SIZED | SUBSIZED | IMMUTABLE;
    }
}

我们可以使用此spliterator来创建流,如下所示:

StreamSupport.stream(SizedSuppliedSpliterator.of(supplier, N_ELEMENTS), true)

当然,计算几个整数并不昂贵,而且我还没有注意到甚至没有考虑到解决方案1的性能改进。