使用java 8流考虑以下愚蠢的程序:
private int biggestInt;
private void run() {
ExecutorService executor = Executors.newWorkStealingPool();
List<Callable<Integer>> callables = new ArrayList<>();
for (int i = 0; i<50; i++) {
callables.add(randomInt());
}
try {
executor.invokeAll(callables)
.stream()
.map(future -> {
try {
return future.get();
} catch (Exception e) {
throw new IllegalStateException(e);
}
})
.forEach(this::compareBiggestInt);
} catch (InterruptedException e) { /* do nothing */ }
}
private Callable<Integer> randomInt() {
return () -> {
Random random = new Random(System.currentTimeMillis());
return random.nextInt();
};
}
private void compareBiggestInt(Integer in) {
if (in > biggestInt)
biggestInt = in;
}
我的问题是,forEach(this :: compareBiggestInt)是并行执行的,因此会在bigInt上引入竞争条件?
如果是这样,我怎样才能避免这种竞争条件? 我可以举例如下更改方法吗?
private synchronized void compareBiggestInt(Integer in) {[...]}
感谢任何帮助!
答案 0 :(得分:2)
不,forEach
不是并行执行的。这会打破forEach
与stream()
相比parallelStream()
使用时预期行为的总体合约,并且不会受到您引入{ExecutorService
的影响{1}}。
invokeAll()
实际上会返回List
{em}已完成或已超时的Future
个实例。因此,并行部分已在您完成时完成与您的流互动。
答案 1 :(得分:2)
并行流中未执行forEach
。实际执行异步任务的是executor
。 Stream#map
操作将一直等到所有Future
完成。
IF 您希望在并行流中执行操作,您应该使用reduction operation:Stream#reduce。例如:
biggestInt = executor.invokeAll(callables)
.parallelStream()
.map(...)// same with yours
.reduce(BinaryOperator.maxBy(Comparator.naturalOrder()))
.orElse(null);
答案 2 :(得分:2)
这里有一些问题。第一:
return () -> {
Random random = new Random(System.currentTimeMillis());
return random.nextInt();
};
执行可以这么快(我可以很容易地重现),这将始终返回相同的值。
我建议您至少删除millis
:
private static Callable<Integer> randomInt() {
return () -> {
Random random = new Random();
int x = random.nextInt(100);
System.out.println(x);
return x;
};
}
甚至更好地使用ThreadLocalRandom.current().nextInt(100)
我还更改了nextInt
以在[0.. 100]
范围内返回,因为nextInt
可能会返回一个负值并想象您返回50个负值然后您的最大值是zero
的{{1}}(默认值);这显然是错误的。
然后您的信息流为biggestInt
,并在每个sequential
操作中阻止,直到map
完成。所以你的Future.get
由一个线程执行。
答案 3 :(得分:0)
您不使用parallel
流,因此您的流是顺序的。如果您想确保您的信息流已完成,请按顺序添加.sequential()
方法。
来自docs:
default Stream<E> stream()
Returns a sequential Stream with this collection as its source.
答案 4 :(得分:0)
假设您并行运行流(我将代码更改为使用“parallelStream”),您必须保护对共享可变变量的所有更改。
例如,在下面的代码中,我在方法“compareBiggestInt”中使用“synchronized”来保护对变量“largestInt”的所有访问。 (如果删除“synchronized”并运行下面的代码,您可以看到方法“compareBiggestInt”确实存在争用条件)
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ParallelStreamExample {
private volatile int biggestInt;
public static void main(String[] args) {
ParallelStreamExample parallelStreamExample = new ParallelStreamExample();
parallelStreamExample.doTheWork();
}
private void doTheWork() {
ExecutorService executor = Executors.newWorkStealingPool();
List<Callable<Integer>> callables = new ArrayList<>();
for (int i = 0; i < 5; i++) {
callables.add(randomInt());
}
try {
executor.invokeAll(callables)
.parallelStream()
.map(future -> {
try {
return future.get();
} catch (Exception e) {
throw new IllegalStateException(e);
}
})
.forEach(this::compareBiggestInt);
} catch (InterruptedException e) { /* do nothing */ }
}
private Callable<Integer> randomInt() {
return () -> {
Random random = new Random();
return random.nextInt(10);
};
}
private synchronized void compareBiggestInt(Integer in) {
System.out.println("in:" + in + " - current biggestint = " + biggestInt);
if (in > biggestInt) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
biggestInt = in;
}
System.out.println("in:" + in + " - current biggestint = " + biggestInt);
}
}