从迭代器创建的CompletableFuture流不会被延迟评估

时间:2018-05-16 06:26:39

标签: java java-stream completable-future spliterator

我对可完成期货的完成方式和时间进行了一些努力。我创建了这个测试用例:

var result = ExecuteStoredProc("SPToBeExecuted");

var spResult = result[0].Cast<Customer>().ToArray();

//Operations performed using spResult
//error occurs in operation performed using spResult

//commit Transaction

输出是:

import org.junit.Test;

import java.util.Arrays;
import java.util.Iterator;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

public class StreamOfCompletableFuturesTest {
    @Test
    public void testList() {
        completeFirstTwoElements(
                Stream.of("list one", "list two", "list three", "list four", "list five")
        );
    }

    @Test
    public void testIterator() {
        Iterator<String> iterator = Arrays.asList("iterator one", "iterator two", "iterator three", "iterator four", "iterator five").iterator();

        completeFirstTwoElements(
            StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), false)
        );
    }

    private void completeFirstTwoElements(Stream<String> stream) {
        stream
                .map(this::cf)
                .limit(2)
                .parallel()
                .forEach(cf -> {
                    try {
                        System.out.println(cf.get());
                    } catch (InterruptedException | ExecutionException e) {
                        throw new RuntimeException(e);
                    }
                });
    }

    private CompletableFuture<String> cf(String result) {
        return CompletableFuture.supplyAsync(() -> {
            System.out.println("Running " + result);
            return result;
        });
    }
}

Running list one Running list two list two list one Running iterator one Running iterator two Running iterator three Running iterator four Running iterator five iterator two iterator one 方法按预期工作。 testList仅在最后评估,因此在限制方法仅保留前两项后。

但是,CompletableFuture方法是意外的。所有testIterator都已完成,限制仅在之后完成。

如果我从流中删除CompletableFuture方法,它会按预期工作。但是,处理(parallel())应该并行完成,因为在我的完整程序中,这是一个长期运行的方法。

任何人都可以解释为什么会这样吗?

看起来这取决于Java版本,所以我在1.8:

forEach()

2 个答案:

答案 0 :(得分:4)

并行性适用于整个管道,因此在并行limit()中应用Stream之前,您无法真正控制执行的内容。唯一的保证是limit()之后的内容只会在保留的元素上执行。

两者之间的差异可能是由于某些实现细节或其他Stream特征。实际上,您可以通过播放SIZED特征轻松地反转行为。当Stream具有已知大小时,似乎只处理了2个元素。

例如,应用简单的filter()会丢失列表版本的大小:

completeFirstTwoElements(
        Stream.of("list one", "list two", "list three", "list four", "list five").filter(a -> true)
);

输出例如:

Running list one
Running list five
Running list two
Running list three
list one
list two

并且未使用未知大小版本的Spliterator.spliterator()“修复了”行为:

Iterator<String> iterator = Arrays.asList("iterator one", "iterator two", "iterator three", "iterator four", "iterator five").iterator();

completeFirstTwoElements(
        StreamSupport.stream(Spliterators.spliterator(iterator, Spliterator.ORDERED, 5), false)
);

输出:

Running iterator two
Running iterator one
iterator one
iterator two

答案 1 :(得分:4)

您的声明“所有CompletableFuture已完成”相当于“所有CompletableFuture已创建”,因为执行supplyAsync后,已安排对供应商的评估,无论是否有人最终会调用get

所以你在这里看到的是对传递给map的函数的评估,即使后续处理不会消耗结果。这是一种有效的行为;只要Stream在之后使用正确的结果,就限制和遭遇顺序而言,可以按照任意顺序或甚至同时对函数进行评估以获得超出必要的元素。

现在,正如“Internal changes for limit and unordered stream”中所讨论的那样,是否会发生评估多于必要的元素以及处理多少多余元素,是一个实现细节,实现已经改变。虽然Q&amp; A是关于无序流的,但对于有序流进行类似的改进似乎是合理的。

需要注意的是,您不应该假设仅针对所需元素的最小数量来评估函数。这样做会降低并行处理的效率。即使Java 9改进了并行limit操作,这仍然适用。一个简单的改变可能会重新引入更多元素的评估:

private void completeFirstTwoElements(Stream<String> stream) {
    stream.map(this::cf)
          .filter(x -> true)
          .limit(2)
          .parallel()
          .forEach(cf -> System.out.println(cf.join()));
}