我想知道以下是否有效:
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
例外情况或其他问题会出错?
答案 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...
由于同步,保持原子值可能最终会变慢(与求和和设置相比),但它会为您提供并行流的单次遍历。