簡體   English   中英

Java Stream API如何選擇執行計划?

[英]How does the Java Stream API select an execution plan?

我剛開始學習Java 8和一般功能編程中的Stream API,但對Java卻不是新手。 我對了解和理解Stream API如何選擇執行計划感興趣。

它如何知道要並行化哪些部分以及不並行化哪些部分? 甚至有多少種執行計划?

基本上,我想知道Java 8中的Streams為什么有助於使事情更快,以及它如何完成某些“魔術”。

我找不到太多有關這一切如何運作的文獻。

這個問題可以廣泛解釋,但我會盡力使它滿意。 我也使用ArrayList流的示例。

當我們創建流時,返回的對象稱為ReferencePipeline 可以說這個對象是“默認流”對象,因為它還沒有任何功能。 現在我們必須在懶惰和渴望的方法之間做出選擇。 因此,讓我們來看一個示例。

示例一: filter(Predicate<?>)方法:

filter()方法的聲明如下:

@Override
public final Stream<P_OUT> filter(Predicate<? super P_OUT> predicate) {
    Objects.requireNonNull(predicate);
    return new StatelessOp<P_OUT, P_OUT>(this, StreamShape.REFERENCE,
                                 StreamOpFlag.NOT_SIZED) {
        @Override
        Sink<P_OUT> opWrapSink(int flags, Sink<P_OUT> sink) {
            return new Sink.ChainedReference<P_OUT, P_OUT>(sink) {
                @Override
                public void begin(long size) {
                    downstream.begin(-1);
                }

                @Override
                public void accept(P_OUT u) {
                    if (predicate.test(u))
                        downstream.accept(u);
                }
            };
        }
    };
}

如您所見,它返回一個StatelessOp對象,該對象基本上是一個新的ReferencePipeline,現在已“啟用”過濾器評估。 換句話說:每次我們向流中添加新的“功能”時,它都會基於舊的流水線並使用適當的操作標志/方法替代來創建新的流水線。
您可能已經知道,只有在調用急切操作之前,流才會被評估。 因此,我們需要一種急切的方法來評估流。

示例二: forEach(Consumer<?>)方法:

@Override
public void forEach(Consumer<? super P_OUT> action) {
    evaluate(ForEachOps.makeRef(action, false));
}

剛開始時這很短, evaluate()方法只不過是調用invoke()方法而已。 在這里,重要的是要了解ForEachOps.makeRef()作用。 它設置最后一個必要的標志,並創建一個與ForkJoinTask對象完全相同的ForEachTask<> 令人高興的是, 安德魯(Andrew)找到了一篇有關它們如何工作的不錯的論文


注意:確切的源代碼可以在這里找到。

您可能已經知道,Stream API使用SpliteratorForkJoinPool來執行並行計算。 Spliterator器用於遍歷和划分元素序列,而ForkJoinPool框架將任務遞歸地分成較小的獨立子任務,直到它們足夠簡單以至於可以異步執行。

作為並行計算框架(例如java.util.stream包)如何在並行計算java.util.stream使用SpliteratorForkJoinPool Spliterator ,這是一種實現關聯的並行forEach ,該方法說明了主要用法:

public static void main(String[] args) {
    List<Integer> list = new SplittableRandom()
        .ints(24, 0, 100)
        .boxed().collect(Collectors.toList());

    parallelEach(list, System.out::println);
}

static <T> void parallelEach(Collection<T> c, Consumer<T> action) {
    Spliterator<T> s = c.spliterator();
    long batchSize = s.estimateSize() / (ForkJoinPool.getCommonPoolParallelism() * 8);
    new ParallelEach(null, s, action, batchSize).invoke(); // invoke the task
}

叉加入任務:

static class ParallelEach<T> extends CountedCompleter<Void> {
    final Spliterator<T> spliterator;
    final Consumer<T> action;
    final long batchSize;

    ParallelEach(ParallelEach<T> parent, Spliterator<T> spliterator,
                 Consumer<T> action, long batchSize) {
        super(parent);
        this.spliterator = spliterator;
        this.action = action;
        this.batchSize = batchSize;
    }

    // The main computation performed by this task
    @Override
    public void compute() {
        Spliterator<T> sub;
        while (spliterator.estimateSize() > batchSize &&
              (sub = spliterator.trySplit()) != null) {
            addToPendingCount(1);
            new ParallelEach<>(this, sub, action, batchSize).fork();
        }
        spliterator.forEachRemaining(action);
        propagateCompletion();
    }
}

原始來源。

另外,請記住,並行計算不一定總是比順序計算快,並且您始終可以選擇- 何時使用並行流

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM