多个线程在单个流中工作

时间:2018-06-18 10:25:27

标签: java multithreading concurrency java-8 java-stream

我想知道以下是否有效:

class SomeCalc {

    AtomicLong   someResultA;
    AtomicDouble someResultB;

    public SomeCalc(List<Something> someList) {

        final Stream<Something> someStream = someList.stream();

        new Thread(() -> {
            long result = someStream.mapToLong(something -> something.getSomeLong()).sum();
            someResultA.set(result);
        }).start();

        new Thread(() -> {
            double result = someStream.mapToDouble(something -> (double) something.getSomeLong() / something.getAnotherLong()).sum();
            someResultB.set(result);
        }).start();
    }
}

只要结果如本例中的原子那样,这会很好吗?或者在此过程中是否会出现ConcurrentAccessModification例外情况或其他问题会出错?

2 个答案:

答案 0 :(得分:7)

它不会工作。你会得到一个:

IllegalStateException: Stream has already been operated upon or closed

因为每个流只能遍历一次。这也可以在java.util.stream.Stream<T>的{​​{3}}中阅读:

  

只有 才能 。例如,这排除了“分叉”流,其中相同的源提供两个或更多个管道,或者同一个流的多个遍历。如果流实现检测到正在重用流,则可能会抛出IllegalStateException。但是,由于某些流操作可能会返回其接收器而不是新的流对象,因此可能无法在所有情况下检测重用。

我猜你的例子很简单。所以你正在准备流,然后让2个线程做额外的东西。

如果你还想保留它。您可以创建该流的供应商,然后让线程获得2个不同的实例:

final Supplier<Stream<Something>> provider = () -> someList.stream(); // and maybe more operations

new Thread(() -> {
    long result = provider.get()  // get an instance
       .mapToLong(something -> something.getSomeLong())
       .sum();
    someResultA.set(result);
}).start();

new Thread(() -> {
    double result = provider.get() // get another instance
         .mapToDouble(something -> (double) something.getSomeLong() / something.getAnotherLong())
         .sum();
    someResultB.set(result);
}).start();

答案 1 :(得分:2)

并发修改的可能性更不用说,这个代码不会起作用,因为它在同一个流上调用多个终端操作。

两个线程最终会在同一个流上调用sum两次,这是不允许的。但是不必担心同时修改原子长对象,因为它设计用于处理来自多个线程的修改调用。

由于您已经在使用原子数据结构,因此您可以将其更改为类似的内容,以使其在一次遍历中工作:

someList.stream()
.mapToLong(something -> something.getSomeLong())
.forEach(entry -> {
    someResultA.incrementAndGet(something.getSomeLong(entry));

    //I assume the below call exists...
    someResultB.incrementAndGet((double) something.getSomeLong() / something.getAnotherLong());
});

如果使用不同线程的原因是并行性,那么您可以将其设为并行流:

someList.stream().parallel()
 .map...

由于同步,保持原子值可能最终会变慢(与求和和设置相比),但它会为您提供并行流的单次遍历。